]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-140550: Update xxlimited with 3.15 limited API (GH-142827)
authorPetr Viktorin <encukou@gmail.com>
Wed, 13 May 2026 16:35:50 +0000 (18:35 +0200)
committerGitHub <noreply@github.com>
Wed, 13 May 2026 16:35:50 +0000 (18:35 +0200)
16 files changed:
Lib/test/test_xxlimited.py
Modules/Setup
Modules/Setup.stdlib.in
Modules/xxlimited.c
Modules/xxlimited_35.c
Modules/xxlimited_3_13.c [new file with mode: 0644]
PC/layout/main.py
PCbuild/pcbuild.proj
PCbuild/readme.txt
PCbuild/xxlimited_3_13.vcxproj [new file with mode: 0644]
PCbuild/xxlimited_3_13.vcxproj.filters [new file with mode: 0644]
Tools/build/generate_stdlib_module_names.py
Tools/c-analyzer/c_parser/preprocessor/gcc.py
Tools/c-analyzer/cpython/_analyzer.py
configure
configure.ac

index b52e78bc4fb7e05183bdee0b2441f0e78ca5a81c..c6e9dc375d9a67654dd1ad984674fa348cd52fad 100644 (file)
@@ -1,19 +1,39 @@
 import unittest
 from test.support import import_helper
-import types
 
 xxlimited = import_helper.import_module('xxlimited')
-xxlimited_35 = import_helper.import_module('xxlimited_35')
 
-
-class CommonTests:
-    module: types.ModuleType
-
-    def test_xxo_new(self):
-        xxo = self.module.Xxo()
-
-    def test_xxo_attributes(self):
-        xxo = self.module.Xxo()
+# if import of xxlimited succeeded, the other ones should be importable.
+import xxlimited_3_13
+import xxlimited_35
+
+MODULES = {
+    (3, 15): xxlimited,
+    (3, 13): xxlimited_3_13,
+    (3, 5): xxlimited_35,
+}
+
+def test_with_xxlimited_modules(since=None, until=None):
+    def _decorator(func):
+        def _wrapper(self, *args, **kwargs):
+            for version, module in MODULES.items():
+                if since and version < since:
+                    continue
+                if until and version >= until:
+                    continue
+                with self.subTest(version=version):
+                    func(self, module, *args, **kwargs)
+        return _wrapper
+    return _decorator
+
+class XXLimitedTests(unittest.TestCase):
+    @test_with_xxlimited_modules()
+    def test_xxo_new(self, module):
+        xxo = module.Xxo()
+
+    @test_with_xxlimited_modules()
+    def test_xxo_attributes(self, module):
+        xxo = module.Xxo()
         with self.assertRaises(AttributeError):
             xxo.foo
         with self.assertRaises(AttributeError):
@@ -26,40 +46,61 @@ class CommonTests:
         with self.assertRaises(AttributeError):
             xxo.foo
 
-    def test_foo(self):
+    @test_with_xxlimited_modules()
+    def test_foo(self, module):
         # the foo function adds 2 numbers
-        self.assertEqual(self.module.foo(1, 2), 3)
+        self.assertEqual(module.foo(1, 2), 3)
 
-    def test_str(self):
-        self.assertIsSubclass(self.module.Str, str)
-        self.assertIsNot(self.module.Str, str)
+    @test_with_xxlimited_modules()
+    def test_str(self, module):
+        self.assertIsSubclass(module.Str, str)
+        self.assertIsNot(module.Str, str)
 
-        custom_string = self.module.Str("abcd")
+        custom_string = module.Str("abcd")
         self.assertEqual(custom_string, "abcd")
         self.assertEqual(custom_string.upper(), "ABCD")
 
-    def test_new(self):
-        xxo = self.module.new()
+    @test_with_xxlimited_modules()
+    def test_new(self, module):
+        xxo = module.new()
         self.assertEqual(xxo.demo("abc"), "abc")
 
-
-class TestXXLimited(CommonTests, unittest.TestCase):
-    module = xxlimited
-
-    def test_xxo_demo(self):
-        xxo = self.module.Xxo()
-        other = self.module.Xxo()
+    @test_with_xxlimited_modules()
+    def test_xxo_demo(self, module):
+        xxo = module.Xxo()
         self.assertEqual(xxo.demo("abc"), "abc")
+        self.assertEqual(xxo.demo(0), None)
+        self.assertEqual(xxo.__module__, module.__name__)
+        with self.assertRaises(TypeError):
+            module.Xxo('arg')
+        with self.assertRaises(TypeError):
+            module.Xxo(kwarg='arg')
+
+    @test_with_xxlimited_modules(since=(3, 13))
+    def test_xxo_demo_extra(self, module):
+        xxo = module.Xxo()
+        other = module.Xxo()
         self.assertEqual(xxo.demo(xxo), xxo)
         self.assertEqual(xxo.demo(other), other)
-        self.assertEqual(xxo.demo(0), None)
 
-    def test_error(self):
-        with self.assertRaises(self.module.Error):
-            raise self.module.Error
-
-    def test_buffer(self):
-        xxo = self.module.Xxo()
+    @test_with_xxlimited_modules(since=(3, 15))
+    def test_xxo_subclass(self, module):
+        class Sub(module.Xxo):
+            pass
+        sub = Sub()
+        sub.a = 123
+        self.assertEqual(sub.a, 123)
+        with self.assertRaisesRegex(AttributeError, "cannot set 'reserved'"):
+            sub.reserved = 123
+
+    @test_with_xxlimited_modules(since=(3, 13))
+    def test_error(self, module):
+        with self.assertRaises(module.Error):
+            raise module.Error
+
+    @test_with_xxlimited_modules(since=(3, 13))
+    def test_buffer(self, module):
+        xxo = module.Xxo()
         self.assertEqual(xxo.x_exports, 0)
         b1 = memoryview(xxo)
         self.assertEqual(xxo.x_exports, 1)
@@ -69,21 +110,13 @@ class TestXXLimited(CommonTests, unittest.TestCase):
         self.assertEqual(b1[0], 1)
         self.assertEqual(b2[0], 1)
 
-
-class TestXXLimited35(CommonTests, unittest.TestCase):
-    module = xxlimited_35
-
-    def test_xxo_demo(self):
-        xxo = self.module.Xxo()
-        other = self.module.Xxo()
-        self.assertEqual(xxo.demo("abc"), "abc")
-        self.assertEqual(xxo.demo(0), None)
-
+    @test_with_xxlimited_modules(until=(3, 5))
     def test_roj(self):
         # the roj function always fails
         with self.assertRaises(SystemError):
             self.module.roj(0)
 
+    @test_with_xxlimited_modules(until=(3, 5))
     def test_null(self):
         null1 = self.module.Null()
         null2 = self.module.Null()
index 33737c21cb4066e4cb32c21e99ee82e8f5f1b29e..e97a78e628693dcf483d115ff1d962c8382f2a63 100644 (file)
@@ -273,6 +273,7 @@ PYTHONPATH=$(COREPYTHONPATH)
 #xx xxmodule.c
 #xxlimited xxlimited.c
 #xxlimited_35 xxlimited_35.c
+#xxlimited_3_13 xxlimited_3_13.c
 #xxsubtype xxsubtype.c
 
 # Testing
index c7a2cda5af69d4e030b7b22427b19560b5211626..c3dd47a5e40a6756e1d345172f50e87be1547599 100644 (file)
 # Limited API template modules; must be built as shared modules.
 @MODULE_XXLIMITED_TRUE@xxlimited xxlimited.c
 @MODULE_XXLIMITED_35_TRUE@xxlimited_35 xxlimited_35.c
+@MODULE_XXLIMITED_3_13_TRUE@xxlimited_3_13 xxlimited_3_13.c
 
 
 # for performance
index 09c8d9487f542664ca8a8e0114829d305a627191..96454ee5e83eab7c841bd7c7d6270c6033410553 100644 (file)
    other files, you'll have to create a file "foobarobject.h"; see
    floatobject.h for an example.
 
-   This module roughly corresponds to::
+   This module uses Limited API 3.15.
+   See ``xxlimited_3_13.c`` if you want to support older CPython versions.
+
+   This module roughly corresponds to the following.
+   (All underscore-prefixed attributes are not accessible from Python.)
+
+   ::
 
       class Xxo:
          """A class that explicitly stores attributes in an internal dict
@@ -27,6 +33,8 @@
               return self._x_attr[name]
 
           def __setattr__(self, name, value):
+              if name == "reserved":
+                  raise AttributeError("cannot set 'reserved'")
               self._x_attr[name] = value
 
           def __delattr__(self, name):
           pass
    */
 
-// Need limited C API version 3.13 for Py_mod_gil
-#include "pyconfig.h"   // Py_GIL_DISABLED
-#ifndef Py_GIL_DISABLED
-#  define Py_LIMITED_API 0x030d0000
-#endif
+// Target both flavors of the Stable ABI.
+// Both are set to version 3.15, which adds PyModExport
+// (When using a build tool, check if it has an option to set these
+//  so they do not need to be defined in the source.)
+#define Py_LIMITED_API 0x030f0000   // abi3 (GIL-enabled builds)
+#define Py_TARGET_ABI3T 0x030f0000  // abi3t (free-threaded builds)
+
 
 #include "Python.h"
 #include <string.h>
 
 // Module state
 typedef struct {
-    PyObject *Xxo_Type;    // Xxo class
+    PyTypeObject *Xxo_Type;    // Xxo class
     PyObject *Error_Type;       // Error class
 } xx_state;
 
 
-/* Xxo objects */
+/* Xxo objects.
+ *
+ * A non-trivial extension type, intentionally showing a number of features
+ * that aren't easy to implement in the Limited API.
+ */
+
+// Forward declaration
+static PyType_Spec Xxo_Type_spec;
+
+// Get the module state (xx_state*) from a given type object 'type', which
+// must be a subclass of Xxo (the type we're defining).
+// This is complicated by the fact that the Xxo type is dynamically allocated,
+// and there may be several such types in a given Python process -- for
+// example, in different subinterpreters, or through loading this
+// extension module several times.
+// So, we don't have a "global" pointer to the type, or to the module, etc.;
+// instead we search based on `Xxo_Type_spec` (which is static, immutable,
+// and process-global).
+//
+// When possible, it's better to avoid `PyType_GetBaseByToken` -- for an
+// example, see the `demo` method (Xxo_demo C function), which uses a
+// "defining class". But, in many cases it's the best solution.
+static xx_state *
+Xxo_state_from_type(PyTypeObject *type)
+{
+    PyTypeObject *base;
+    // Search all superclasses of 'type' for one that was defined using
+    // "Xxo_Type_spec". That must be our 'Xxo' class.
+    if (PyType_GetBaseByToken(type, &Xxo_Type_spec, &base) < 0) {
+        return NULL;
+    }
+    if (base == NULL) {
+        PyErr_SetString(PyExc_TypeError, "need Xxo subclass");
+        return NULL;
+    }
+    // From this type, get the associated module. That must be the
+    // relevant `xxlimited` module.
+    xx_state *state = PyType_GetModuleState(base);
+    Py_DECREF(base);
+    return state;
+}
 
-// Instance state
+// Structure for data needed by the XxoObject type.
+// Since the object may be shared across threads, access to the fields
+// usually needs to be synchronized (using Py_BEGIN_CRITICAL_SECTION).
 typedef struct {
-    PyObject_HEAD
-    PyObject            *x_attr;           /* Attributes dictionary.
-                                            * May be NULL, which acts as an
-                                            * empty dict.
-                                            */
-    char                x_buffer[BUFSIZE]; /* buffer for Py_buffer */
-    Py_ssize_t          x_exports;         /* how many buffer are exported */
-} XxoObject;
-
-#define XxoObject_CAST(op)  ((XxoObject *)(op))
-// TODO: full support for type-checking was added in 3.14 (Py_tp_token)
-// #define XxoObject_Check(v)      Py_IS_TYPE(v, Xxo_Type)
-
-static XxoObject *
-newXxoObject(PyObject *module)
+    PyObject   *x_attr;           /* Attributes dictionary.
+                                   * May be NULL, which acts as an
+                                   * empty dict.
+                                   */
+    Py_ssize_t x_exports;         /* how many buffers are exported */
+    char       x_buffer[BUFSIZE]; /* buffer for Py_buffer (for simplicity,
+                                   * this is constant, so does not need
+                                   * synchronization)
+                                   */
+} XxoObject_Data;
+
+// Get the `XxoObject_Data` structure for a given instance of our type.
+static XxoObject_Data *
+Xxo_get_data(PyObject *self)
 {
-    xx_state *state = PyModule_GetState(module);
+    xx_state *state = Xxo_state_from_type(Py_TYPE(self));
+    if (!state) {
+        return NULL;
+    }
+    XxoObject_Data *data = PyObject_GetTypeData(self, state->Xxo_Type);
+    return data;
+}
+
+// A variant of Xxo_get_data to be used in the tp_traverse handler.
+// This function cannot have side effects (including reference count
+// manipulation, creating objects, and raising exceptions), and must not
+// call API functions that might have side effects.
+// See: https://docs.python.org/3.15/c-api/gcsupport.html#traversal
+static XxoObject_Data *
+Xxo_get_data_DuringGC(PyObject *self)
+{
+    PyTypeObject *base;
+    PyType_GetBaseByToken_DuringGC(Py_TYPE(self), &Xxo_Type_spec, &base);
+    if (base == NULL) {
+        return NULL;
+    }
+    xx_state *state = PyType_GetModuleState_DuringGC(base);
     if (state == NULL) {
         return NULL;
     }
-    XxoObject *self;
-    self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
+    XxoObject_Data *data = PyObject_GetTypeData_DuringGC(self, state->Xxo_Type);
+    return data;
+}
+
+// Xxo initialization
+// This is the implementation of Xxo.__new__
+static PyObject *
+Xxo_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    // Validate that we did not get any arguments.
+    if ((args != NULL && PyObject_Length(args))
+        || (kwargs != NULL && PyObject_Length(kwargs)))
+    {
+        PyErr_SetString(PyExc_TypeError, "Xxo.__new__() takes no arguments");
+        return NULL;
+    }
+    // Create an instance of *type* (which may be a subclass)
+    allocfunc alloc = PyType_GetSlot(type, Py_tp_alloc);
+    PyObject *self = alloc(type, 0);
     if (self == NULL) {
         return NULL;
     }
-    self->x_attr = NULL;
-    memset(self->x_buffer, 0, BUFSIZE);
-    self->x_exports = 0;
+
+    // Initialize the C members on the instance.
+    // This is only included for the sake of example. The default alloc
+    // function zeroes instance memory; we don't need to do it again.
+    // Note that we during initialization (and finalization), we hold the only
+    // reference to the object, so we don't need to synchronize with
+    // other threads.
+    XxoObject_Data *xxo_data = Xxo_get_data(self);
+    if (xxo_data == NULL) {
+        Py_DECREF(self);
+        return NULL;
+    }
+
+    xxo_data->x_attr = NULL;
+    memset(xxo_data->x_buffer, 0, BUFSIZE);
+    xxo_data->x_exports = 0;
     return self;
 }
 
@@ -125,45 +227,63 @@ newXxoObject(PyObject *module)
 
 // traverse: Visit all references from an object, including its type
 static int
-Xxo_traverse(PyObject *op, visitproc visit, void *arg)
+Xxo_traverse(PyObject *self, visitproc visit, void *arg)
 {
     // Visit the type
-    Py_VISIT(Py_TYPE(op));
+    Py_VISIT(Py_TYPE(self));
 
     // Visit the attribute dict
-    XxoObject *self = XxoObject_CAST(op);
-    Py_VISIT(self->x_attr);
+    XxoObject_Data *data = Xxo_get_data_DuringGC(self);
+    if (data == NULL) {
+        return 0;
+    }
+    Py_VISIT(data->x_attr);
     return 0;
 }
 
 // clear: drop references in order to break all reference cycles
 static int
-Xxo_clear(PyObject *op)
+Xxo_clear(PyObject *self)
 {
-    XxoObject *self = XxoObject_CAST(op);
-    Py_CLEAR(self->x_attr);
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return 0;
+    }
+    Py_CLEAR(data->x_attr);
     return 0;
 }
 
 // finalize: like clear, but should leave the object in a consistent state.
 // Equivalent to `__del__` in Python.
 static void
-Xxo_finalize(PyObject *op)
+Xxo_finalize(PyObject *self)
 {
-    XxoObject *self = XxoObject_CAST(op);
-    Py_CLEAR(self->x_attr);
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return;
+    }
+    Py_CLEAR(data->x_attr);
 }
 
 // dealloc: drop all remaining references and free memory
 static void
 Xxo_dealloc(PyObject *self)
 {
+    // This function must preserve currently raised exception, if any.
+    PyObject *exc = PyErr_GetRaisedException();
+
     PyObject_GC_UnTrack(self);
     Xxo_finalize(self);
+
     PyTypeObject *tp = Py_TYPE(self);
     freefunc free = PyType_GetSlot(tp, Py_tp_free);
     free(self);
     Py_DECREF(tp);
+
+    if (PyErr_Occurred()) {
+        PyErr_WriteUnraisable(NULL);
+    }
+    PyErr_SetRaisedException(exc);
 }
 
 
@@ -171,11 +291,20 @@ Xxo_dealloc(PyObject *self)
 
 // Get an attribute.
 static PyObject *
-Xxo_getattro(PyObject *op, PyObject *name)
+Xxo_getattro(PyObject *self, PyObject *name)
 {
-    XxoObject *self = XxoObject_CAST(op);
-    if (self->x_attr != NULL) {
-        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return 0;
+    }
+
+    PyObject *x_attr;
+    Py_BEGIN_CRITICAL_SECTION(self);
+    x_attr = data->x_attr;
+    Py_END_CRITICAL_SECTION();
+
+    if (x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(x_attr, name);
         if (v != NULL) {
             return Py_NewRef(v);
         }
@@ -185,24 +314,42 @@ Xxo_getattro(PyObject *op, PyObject *name)
     }
     // Fall back to generic implementation (this handles special attributes,
     // raising AttributeError, etc.)
-    return PyObject_GenericGetAttr(op, name);
+    return PyObject_GenericGetAttr(self, name);
 }
 
 // Set or delete an attribute.
 static int
-Xxo_setattro(PyObject *op, PyObject *name, PyObject *v)
+Xxo_setattro(PyObject *self, PyObject *name, PyObject *v)
 {
-    XxoObject *self = XxoObject_CAST(op);
-    if (self->x_attr == NULL) {
+    // filter a specific attribute name
+    if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "reserved")) {
+        PyErr_Format(PyExc_AttributeError, "cannot set %R", name);
+        return -1;
+    }
+
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return -1;
+    }
+
+    // If the attribute dict is not created yet, make one.
+    // This needs to be protected by a critical section to avoid another thread
+    // creating a duplicate dict.
+    PyObject *x_attr;
+    Py_BEGIN_CRITICAL_SECTION(self);
+    x_attr = data->x_attr;
+    if (x_attr == NULL) {
         // prepare the attribute dict
-        self->x_attr = PyDict_New();
-        if (self->x_attr == NULL) {
-            return -1;
-        }
+        data->x_attr = x_attr = PyDict_New();
     }
+    Py_END_CRITICAL_SECTION();
+    if (x_attr == NULL) {
+        return -1;
+    }
+
     if (v == NULL) {
         // delete an attribute
-        int rv = PyDict_DelItem(self->x_attr, name);
+        int rv = PyDict_DelItem(x_attr, name);
         if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
             PyErr_SetString(PyExc_AttributeError,
                 "delete non-existing Xxo attribute");
@@ -212,7 +359,7 @@ Xxo_setattro(PyObject *op, PyObject *name, PyObject *v)
     }
     else {
         // set an attribute
-        return PyDict_SetItem(self->x_attr, name, v);
+        return PyDict_SetItem(x_attr, name, v);
     }
 }
 
@@ -221,7 +368,7 @@ Xxo_setattro(PyObject *op, PyObject *name, PyObject *v)
  */
 
 static PyObject *
-Xxo_demo(PyObject *op, PyTypeObject *defining_class,
+Xxo_demo(PyObject *self, PyTypeObject *defining_class,
          PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
 {
     if (kwnames != NULL && PyObject_Length(kwnames)) {
@@ -260,30 +407,49 @@ static PyMethodDef Xxo_methods[] = {
  */
 
 static int
-Xxo_getbuffer(PyObject *op, Py_buffer *view, int flags)
+Xxo_getbuffer(PyObject *self, Py_buffer *view, int flags)
 {
-    XxoObject *self = XxoObject_CAST(op);
-    int res = PyBuffer_FillInfo(view, op,
-                               (void *)self->x_buffer, BUFSIZE,
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return -1;
+    }
+    int res = PyBuffer_FillInfo(view, self,
+                               (void *)data->x_buffer, BUFSIZE,
                                0, flags);
     if (res == 0) {
-        self->x_exports++;
+        Py_BEGIN_CRITICAL_SECTION(self);
+        data->x_exports++;
+        Py_END_CRITICAL_SECTION();
     }
     return res;
 }
 
 static void
-Xxo_releasebuffer(PyObject *op, Py_buffer *Py_UNUSED(view))
+Xxo_releasebuffer(PyObject *self, Py_buffer *Py_UNUSED(view))
 {
-    XxoObject *self = XxoObject_CAST(op);
-    self->x_exports--;
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return;
+    }
+    Py_BEGIN_CRITICAL_SECTION(self);
+    data->x_exports--;
+    Py_END_CRITICAL_SECTION();
 }
 
 static PyObject *
-Xxo_get_x_exports(PyObject *op, void *Py_UNUSED(closure))
+Xxo_get_x_exports(PyObject *self, void *Py_UNUSED(closure))
 {
-    XxoObject *self = XxoObject_CAST(op);
-    return PyLong_FromSsize_t(self->x_exports);
+    XxoObject_Data *data = Xxo_get_data(self);
+    if (data == NULL) {
+        return NULL;
+    }
+    Py_ssize_t result;
+
+    Py_BEGIN_CRITICAL_SECTION(self);
+    result = data->x_exports;
+    Py_END_CRITICAL_SECTION();
+
+    return PyLong_FromSsize_t(result);
 }
 
 /* Xxo type definition */
@@ -299,6 +465,7 @@ static PyGetSetDef Xxo_getsetlist[] = {
 
 static PyType_Slot Xxo_Type_slots[] = {
     {Py_tp_doc, (char *)Xxo_doc},
+    {Py_tp_new, Xxo_new},
     {Py_tp_traverse, Xxo_traverse},
     {Py_tp_clear, Xxo_clear},
     {Py_tp_finalize, Xxo_finalize},
@@ -309,13 +476,14 @@ static PyType_Slot Xxo_Type_slots[] = {
     {Py_bf_getbuffer, Xxo_getbuffer},
     {Py_bf_releasebuffer, Xxo_releasebuffer},
     {Py_tp_getset, Xxo_getsetlist},
+    {Py_tp_token, Py_TP_USE_SPEC},
     {0, 0},  /* sentinel */
 };
 
 static PyType_Spec Xxo_Type_spec = {
     .name = "xxlimited.Xxo",
-    .basicsize = sizeof(XxoObject),
-    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .basicsize = -(Py_ssize_t)sizeof(XxoObject_Data),
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,
     .slots = Xxo_Type_slots,
 };
 
@@ -354,17 +522,17 @@ xx_foo(PyObject *module, PyObject *args)
 }
 
 
-/* Function of no arguments returning new Xxo object */
+/* Function of no arguments returning new Xxo object.
+ * Note that a function exposed to Python with METH_NOARGS requires an unused
+ * second argument, so we cannot use newXxoObject directly.
+ */
 
 static PyObject *
 xx_new(PyObject *module, PyObject *Py_UNUSED(unused))
 {
-    XxoObject *rv;
+    xx_state *state = PyModule_GetState(module);
 
-    rv = newXxoObject(module);
-    if (rv == NULL)
-        return NULL;
-    return (PyObject *)rv;
+    return Xxo_new(state->Xxo_Type, NULL, NULL);
 }
 
 
@@ -398,11 +566,12 @@ xx_modexec(PyObject *m)
         return -1;
     }
 
-    state->Xxo_Type = PyType_FromModuleAndSpec(m, &Xxo_Type_spec, NULL);
+    state->Xxo_Type = (PyTypeObject*)PyType_FromModuleAndSpec(
+        m, &Xxo_Type_spec, NULL);
     if (state->Xxo_Type == NULL) {
         return -1;
     }
-    if (PyModule_AddType(m, (PyTypeObject*)state->Xxo_Type) < 0) {
+    if (PyModule_AddType(m, state->Xxo_Type) < 0) {
         return -1;
     }
 
@@ -410,12 +579,13 @@ xx_modexec(PyObject *m)
     // added to the module dict.
     // It does not inherit from "object" (PyObject_Type), but from "str"
     // (PyUnincode_Type).
-    PyObject *Str_Type = PyType_FromModuleAndSpec(
+    PyTypeObject *Str_Type = (PyTypeObject*)PyType_FromModuleAndSpec(
         m, &Str_Type_spec, (PyObject *)&PyUnicode_Type);
     if (Str_Type == NULL) {
         return -1;
     }
-    if (PyModule_AddType(m, (PyTypeObject*)Str_Type) < 0) {
+    if (PyModule_AddType(m, Str_Type) < 0) {
+        Py_DECREF(Str_Type);
         return -1;
     }
     Py_DECREF(Str_Type);
@@ -423,29 +593,6 @@ xx_modexec(PyObject *m)
     return 0;
 }
 
-static PyModuleDef_Slot xx_slots[] = {
-
-    /* exec function to initialize the module (called as part of import
-     * after the object was added to sys.modules)
-     */
-    {Py_mod_exec, xx_modexec},
-
-    /* Signal that this module supports being loaded in multiple interpreters
-     * with separate GILs (global interpreter locks).
-     * See "Isolating Extension Modules" on how to prepare a module for this:
-     *   https://docs.python.org/3/howto/isolating-extensions.html
-     */
-    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
-
-    /* Signal that this module does not rely on the GIL for its own needs.
-     * Without this slot, free-threaded builds of CPython will enable
-     * the GIL when this module is loaded.
-     */
-    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
-
-    {0, NULL}
-};
-
 // Module finalization: modules that hold references in their module state
 // need to implement the fullowing GC hooks. They're similar to the ones for
 // types (see "Xxo finalization").
@@ -453,7 +600,10 @@ static PyModuleDef_Slot xx_slots[] = {
 static int
 xx_traverse(PyObject *module, visitproc visit, void *arg)
 {
-    xx_state *state = PyModule_GetState(module);
+    xx_state *state = PyModule_GetState_DuringGC(module);
+    if (state == NULL) {
+        return 0;
+    }
     Py_VISIT(state->Xxo_Type);
     Py_VISIT(state->Error_Type);
     return 0;
@@ -463,6 +613,9 @@ static int
 xx_clear(PyObject *module)
 {
     xx_state *state = PyModule_GetState(module);
+    if (state == NULL) {
+        return 0;
+    }
     Py_CLEAR(state->Xxo_Type);
     Py_CLEAR(state->Error_Type);
     return 0;
@@ -473,27 +626,59 @@ xx_free(void *module)
 {
     // allow xx_modexec to omit calling xx_clear on error
     (void)xx_clear((PyObject *)module);
+
+    xx_state *state = PyModule_GetState(module);
+    if (state == NULL) {
+        return;
+    }
 }
 
-static struct PyModuleDef xxmodule = {
-    PyModuleDef_HEAD_INIT,
-    .m_name = "xxlimited",
-    .m_doc = module_doc,
-    .m_size = sizeof(xx_state),
-    .m_methods = xx_methods,
-    .m_slots = xx_slots,
-    .m_traverse = xx_traverse,
-    .m_clear = xx_clear,
-    .m_free = xx_free,
+// Information that CPython uses to prevent loading incompatible extenstions
+PyABIInfo_VAR(abi_info);
+
+static PySlot xx_slots[] = {
+    /* Basic metadata */
+    PySlot_STATIC_DATA(Py_mod_name, "xxlimited"),
+    PySlot_STATIC_DATA(Py_mod_doc, (void*)module_doc),
+    PySlot_DATA(Py_mod_abi, &abi_info),
+
+    /* The method table */
+    PySlot_STATIC_DATA(Py_mod_methods, xx_methods),
+
+    /* exec function to initialize the module (called as part of import
+     * after the object was added to sys.modules)
+     */
+    PySlot_FUNC(Py_mod_exec, xx_modexec),
+
+    /* Module state and associated functions */
+    PySlot_SIZE(Py_mod_state_size, sizeof(xx_state)),
+    PySlot_FUNC(Py_mod_state_traverse, xx_traverse),
+    PySlot_FUNC(Py_mod_state_clear, xx_clear),
+    PySlot_FUNC(Py_mod_state_free, xx_free),
+
+    /* Signal that this module supports being loaded in multiple interpreters
+     * with separate GILs (global interpreter locks).
+     * See "Isolating Extension Modules" on how to prepare a module for this:
+     *   https://docs.python.org/3/howto/isolating-extensions.html
+     */
+    PySlot_DATA(Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED),
+
+    /* Signal that this module does not rely on the GIL for its own needs.
+     * Without this slot, free-threaded builds of CPython will enable
+     * the GIL when this module is loaded.
+     */
+    PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED),
+
+    PySlot_END
 };
 
 
-/* Export function for the module. *Must* be called PyInit_xx; usually it is
- * the only non-`static` object in a module definition.
+/* Export function for the module. *Must* be called PyModExport_xx; usually
+ * it is the only non-`static` object in a module definition.
  */
 
-PyMODINIT_FUNC
-PyInit_xxlimited(void)
+PyMODEXPORT_FUNC
+PyModExport_xxlimited(void)
 {
-    return PyModuleDef_Init(&xxmodule);
+    return xx_slots;
 }
index b0a682ac4e6bb699a12d4203cf98d19b2cfc9c4a..9ef0eac9a924e6c29b57e0480f3fef09dfd15962 100644 (file)
@@ -305,7 +305,7 @@ xx_modexec(PyObject *m)
 static PyModuleDef_Slot xx_slots[] = {
     {Py_mod_exec, xx_modexec},
 #ifdef Py_GIL_DISABLED
-    // These definitions are in the limited API, but not until 3.13.
+    // In a free-threaded build, we don't use Limited API.
     {Py_mod_gil, Py_MOD_GIL_NOT_USED},
 #endif
     {0, NULL}
diff --git a/Modules/xxlimited_3_13.c b/Modules/xxlimited_3_13.c
new file mode 100644 (file)
index 0000000..4f100f9
--- /dev/null
@@ -0,0 +1,499 @@
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your source file should be named
+   foo.c or foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   floatobject.h for an example.
+
+   This module roughly corresponds to::
+
+      class Xxo:
+         """A class that explicitly stores attributes in an internal dict
+         (to simulate custom attribute handling).
+         """
+
+          def __init__(self):
+              # In the C class, "_x_attr" is not accessible from Python code
+              self._x_attr = {}
+              self._x_exports = 0
+
+          def __getattr__(self, name):
+              return self._x_attr[name]
+
+          def __setattr__(self, name, value):
+              self._x_attr[name] = value
+
+          def __delattr__(self, name):
+              del self._x_attr[name]
+
+          @property
+          def x_exports(self):
+              """Return the number of times an internal buffer is exported."""
+              # Each Xxo instance has a 10-byte buffer that can be
+              # accessed via the buffer interface (e.g. `memoryview`).
+              return self._x_exports
+
+          def demo(o, /):
+              if isinstance(o, str):
+                  return o
+              elif isinstance(o, Xxo):
+                  return o
+              else:
+                  raise Error('argument must be str or Xxo')
+
+      class Error(Exception):
+          """Exception raised by the xxlimited module"""
+
+      def foo(i: int, j: int, /):
+          """Return the sum of i and j."""
+          # Unlike this pseudocode, the C function will *only* work with
+          # integers and perform C long int arithmetic
+          return i + j
+
+      def new():
+          return Xxo()
+
+      def Str(str):
+          # A trivial subclass of a built-in type
+          pass
+   */
+
+// Need limited C API version 3.13 for Py_mod_gil
+#include "pyconfig.h"   // Py_GIL_DISABLED
+#ifndef Py_GIL_DISABLED
+#  define Py_LIMITED_API 0x030d0000
+#endif
+
+#include "Python.h"
+#include <string.h>
+
+#define BUFSIZE 10
+
+// Module state
+typedef struct {
+    PyObject *Xxo_Type;    // Xxo class
+    PyObject *Error_Type;       // Error class
+} xx_state;
+
+
+/* Xxo objects */
+
+// Instance state
+typedef struct {
+    PyObject_HEAD
+    PyObject            *x_attr;           /* Attributes dictionary.
+                                            * May be NULL, which acts as an
+                                            * empty dict.
+                                            */
+    char                x_buffer[BUFSIZE]; /* buffer for Py_buffer */
+    Py_ssize_t          x_exports;         /* how many buffer are exported */
+} XxoObject;
+
+#define XxoObject_CAST(op)  ((XxoObject *)(op))
+// TODO: full support for type-checking was added in 3.14 (Py_tp_token)
+// #define XxoObject_Check(v)      Py_IS_TYPE(v, Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *module)
+{
+    xx_state *state = PyModule_GetState(module);
+    if (state == NULL) {
+        return NULL;
+    }
+    XxoObject *self;
+    self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
+    if (self == NULL) {
+        return NULL;
+    }
+    self->x_attr = NULL;
+    memset(self->x_buffer, 0, BUFSIZE);
+    self->x_exports = 0;
+    return self;
+}
+
+/* Xxo finalization.
+ *
+ * Types that store references to other PyObjects generally need to implement
+ * the GC slots: traverse, clear, dealloc, and (optionally) finalize.
+ */
+
+// traverse: Visit all references from an object, including its type
+static int
+Xxo_traverse(PyObject *op, visitproc visit, void *arg)
+{
+    // Visit the type
+    Py_VISIT(Py_TYPE(op));
+
+    // Visit the attribute dict
+    XxoObject *self = XxoObject_CAST(op);
+    Py_VISIT(self->x_attr);
+    return 0;
+}
+
+// clear: drop references in order to break all reference cycles
+static int
+Xxo_clear(PyObject *op)
+{
+    XxoObject *self = XxoObject_CAST(op);
+    Py_CLEAR(self->x_attr);
+    return 0;
+}
+
+// finalize: like clear, but should leave the object in a consistent state.
+// Equivalent to `__del__` in Python.
+static void
+Xxo_finalize(PyObject *op)
+{
+    XxoObject *self = XxoObject_CAST(op);
+    Py_CLEAR(self->x_attr);
+}
+
+// dealloc: drop all remaining references and free memory
+static void
+Xxo_dealloc(PyObject *self)
+{
+    PyObject_GC_UnTrack(self);
+    Xxo_finalize(self);
+    PyTypeObject *tp = Py_TYPE(self);
+    freefunc free = PyType_GetSlot(tp, Py_tp_free);
+    free(self);
+    Py_DECREF(tp);
+}
+
+
+/* Xxo attribute handling */
+
+// Get an attribute.
+static PyObject *
+Xxo_getattro(PyObject *op, PyObject *name)
+{
+    XxoObject *self = XxoObject_CAST(op);
+    if (self->x_attr != NULL) {
+        PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
+        if (v != NULL) {
+            return Py_NewRef(v);
+        }
+        else if (PyErr_Occurred()) {
+            return NULL;
+        }
+    }
+    // Fall back to generic implementation (this handles special attributes,
+    // raising AttributeError, etc.)
+    return PyObject_GenericGetAttr(op, name);
+}
+
+// Set or delete an attribute.
+static int
+Xxo_setattro(PyObject *op, PyObject *name, PyObject *v)
+{
+    XxoObject *self = XxoObject_CAST(op);
+    if (self->x_attr == NULL) {
+        // prepare the attribute dict
+        self->x_attr = PyDict_New();
+        if (self->x_attr == NULL) {
+            return -1;
+        }
+    }
+    if (v == NULL) {
+        // delete an attribute
+        int rv = PyDict_DelItem(self->x_attr, name);
+        if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
+            PyErr_SetString(PyExc_AttributeError,
+                "delete non-existing Xxo attribute");
+            return -1;
+        }
+        return rv;
+    }
+    else {
+        // set an attribute
+        return PyDict_SetItem(self->x_attr, name, v);
+    }
+}
+
+/* Xxo methods: C functions plus a PyMethodDef array that lists them and
+ * specifies metadata.
+ */
+
+static PyObject *
+Xxo_demo(PyObject *op, PyTypeObject *defining_class,
+         PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+    if (kwnames != NULL && PyObject_Length(kwnames)) {
+        PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments");
+        return NULL;
+    }
+    if (nargs != 1) {
+        PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument");
+        return NULL;
+    }
+
+    PyObject *o = args[0];
+
+    /* Test if the argument is "str" */
+    if (PyUnicode_Check(o)) {
+        return Py_NewRef(o);
+    }
+
+    /* test if the argument is of the Xxo class */
+    if (PyObject_TypeCheck(o, defining_class)) {
+        return Py_NewRef(o);
+    }
+
+    return Py_NewRef(Py_None);
+}
+
+static PyMethodDef Xxo_methods[] = {
+    {"demo",            _PyCFunction_CAST(Xxo_demo),
+     METH_METHOD | METH_FASTCALL | METH_KEYWORDS, PyDoc_STR("demo(o) -> o")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+/* Xxo buffer interface: C functions later referenced from PyType_Slot array.
+ * Other interfaces (e.g. for sequence-like or number-like types) are defined
+ * similarly.
+ */
+
+static int
+Xxo_getbuffer(PyObject *op, Py_buffer *view, int flags)
+{
+    XxoObject *self = XxoObject_CAST(op);
+    int res = PyBuffer_FillInfo(view, op,
+                               (void *)self->x_buffer, BUFSIZE,
+                               0, flags);
+    if (res == 0) {
+        self->x_exports++;
+    }
+    return res;
+}
+
+static void
+Xxo_releasebuffer(PyObject *op, Py_buffer *Py_UNUSED(view))
+{
+    XxoObject *self = XxoObject_CAST(op);
+    self->x_exports--;
+}
+
+static PyObject *
+Xxo_get_x_exports(PyObject *op, void *Py_UNUSED(closure))
+{
+    XxoObject *self = XxoObject_CAST(op);
+    return PyLong_FromSsize_t(self->x_exports);
+}
+
+/* Xxo type definition */
+
+PyDoc_STRVAR(Xxo_doc,
+             "A class that explicitly stores attributes in an internal dict");
+
+static PyGetSetDef Xxo_getsetlist[] = {
+    {"x_exports", Xxo_get_x_exports, NULL, NULL},
+    {NULL},
+};
+
+
+static PyType_Slot Xxo_Type_slots[] = {
+    {Py_tp_doc, (char *)Xxo_doc},
+    {Py_tp_traverse, Xxo_traverse},
+    {Py_tp_clear, Xxo_clear},
+    {Py_tp_finalize, Xxo_finalize},
+    {Py_tp_dealloc, Xxo_dealloc},
+    {Py_tp_getattro, Xxo_getattro},
+    {Py_tp_setattro, Xxo_setattro},
+    {Py_tp_methods, Xxo_methods},
+    {Py_bf_getbuffer, Xxo_getbuffer},
+    {Py_bf_releasebuffer, Xxo_releasebuffer},
+    {Py_tp_getset, Xxo_getsetlist},
+    {0, 0},  /* sentinel */
+};
+
+static PyType_Spec Xxo_Type_spec = {
+    .name = "xxlimited_3_13.Xxo",
+    .basicsize = sizeof(XxoObject),
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+    .slots = Xxo_Type_slots,
+};
+
+
+/* Str type definition*/
+
+static PyType_Slot Str_Type_slots[] = {
+    // slots array intentionally kept empty
+    {0, 0},  /* sentinel */
+};
+
+static PyType_Spec Str_Type_spec = {
+    .name = "xxlimited_3_13.Str",
+    .basicsize = 0,
+    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+    .slots = Str_Type_slots,
+};
+
+
+/* Function of two integers returning integer (with C "long int" arithmetic) */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *module, PyObject *args)
+{
+    long i, j;
+    long res;
+    if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+        return NULL;
+    res = i+j; /* XXX Do something here */
+    return PyLong_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *module, PyObject *Py_UNUSED(unused))
+{
+    XxoObject *rv;
+
+    rv = newXxoObject(module);
+    if (rv == NULL)
+        return NULL;
+    return (PyObject *)rv;
+}
+
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+    {"foo",             xx_foo,         METH_VARARGS,
+        xx_foo_doc},
+    {"new",             xx_new,         METH_NOARGS,
+        PyDoc_STR("new() -> new Xx object")},
+    {NULL,              NULL}           /* sentinel */
+};
+
+
+/* The module itself */
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+static int
+xx_modexec(PyObject *m)
+{
+    xx_state *state = PyModule_GetState(m);
+
+    state->Error_Type = PyErr_NewException("xxlimited_3_13.Error", NULL, NULL);
+    if (state->Error_Type == NULL) {
+        return -1;
+    }
+    if (PyModule_AddType(m, (PyTypeObject*)state->Error_Type) < 0) {
+        return -1;
+    }
+
+    state->Xxo_Type = PyType_FromModuleAndSpec(m, &Xxo_Type_spec, NULL);
+    if (state->Xxo_Type == NULL) {
+        return -1;
+    }
+    if (PyModule_AddType(m, (PyTypeObject*)state->Xxo_Type) < 0) {
+        return -1;
+    }
+
+    // Add the Str type. It is not needed from C code, so it is only
+    // added to the module dict.
+    // It does not inherit from "object" (PyObject_Type), but from "str"
+    // (PyUnincode_Type).
+    PyObject *Str_Type = PyType_FromModuleAndSpec(
+        m, &Str_Type_spec, (PyObject *)&PyUnicode_Type);
+    if (Str_Type == NULL) {
+        return -1;
+    }
+    if (PyModule_AddType(m, (PyTypeObject*)Str_Type) < 0) {
+        return -1;
+    }
+    Py_DECREF(Str_Type);
+
+    return 0;
+}
+
+static PyModuleDef_Slot xx_slots[] = {
+
+    /* exec function to initialize the module (called as part of import
+     * after the object was added to sys.modules)
+     */
+    {Py_mod_exec, xx_modexec},
+
+    /* Signal that this module supports being loaded in multiple interpreters
+     * with separate GILs (global interpreter locks).
+     * See "Isolating Extension Modules" on how to prepare a module for this:
+     *   https://docs.python.org/3/howto/isolating-extensions.html
+     */
+    {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
+
+    /* Signal that this module does not rely on the GIL for its own needs.
+     * Without this slot, free-threaded builds of CPython will enable
+     * the GIL when this module is loaded.
+     */
+    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+
+    {0, NULL}
+};
+
+// Module finalization: modules that hold references in their module state
+// need to implement the fullowing GC hooks. They're similar to the ones for
+// types (see "Xxo finalization").
+
+static int
+xx_traverse(PyObject *module, visitproc visit, void *arg)
+{
+    xx_state *state = PyModule_GetState(module);
+    Py_VISIT(state->Xxo_Type);
+    Py_VISIT(state->Error_Type);
+    return 0;
+}
+
+static int
+xx_clear(PyObject *module)
+{
+    xx_state *state = PyModule_GetState(module);
+    Py_CLEAR(state->Xxo_Type);
+    Py_CLEAR(state->Error_Type);
+    return 0;
+}
+
+static void
+xx_free(void *module)
+{
+    // allow xx_modexec to omit calling xx_clear on error
+    (void)xx_clear((PyObject *)module);
+}
+
+static struct PyModuleDef xxmodule = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "xxlimited_3_13",
+    .m_doc = module_doc,
+    .m_size = sizeof(xx_state),
+    .m_methods = xx_methods,
+    .m_slots = xx_slots,
+    .m_traverse = xx_traverse,
+    .m_clear = xx_clear,
+    .m_free = xx_free,
+};
+
+
+/* Export function for the module. *Must* be called PyInit_xx; usually it is
+ * the only non-`static` object in a module definition.
+ */
+
+PyMODINIT_FUNC
+PyInit_xxlimited_3_13(void)
+{
+    return PyModuleDef_Init(&xxmodule);
+}
index 3566b8bd873874c13cff985e06e3c40ecb5d52fb..3a62ea91420c9e2020e4ac464be7a0c72cb4d796 100644 (file)
@@ -32,7 +32,8 @@ from .support.props import *
 from .support.pymanager import *
 from .support.nuspec import *
 
-TEST_PYDS_ONLY = FileStemSet("xxlimited", "xxlimited_35", "_ctypes_test", "_test*")
+TEST_PYDS_ONLY = FileStemSet("xxlimited",  "xxlimited_3_13", "xxlimited_35",
+                             "_ctypes_test", "_test*")
 TEST_DLLS_ONLY = set()
 TEST_DIRS_ONLY = FileNameSet("test", "tests")
 
index bb7d8042176d8f173161744097147157ed852973..9d077bbd3f0ba27087dd082bda3ae33fd9597157 100644 (file)
@@ -84,6 +84,7 @@
     <TestModules Include="_ctypes_test;_testbuffer;_testcapi;_testlimitedcapi;_testinternalcapi;_testembed;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testclinic;_testclinic_limited" />
     <TestModules Include="xxlimited" Condition="'$(Configuration)' == 'Release'" />
     <TestModules Include="xxlimited_35" Condition="'$(Configuration)' == 'Release'" />
+    <TestModules Include="xxlimited_3_13" Condition="'$(Configuration)' == 'Release'" />
     <Projects Include="@(TestModules->'%(Identity).vcxproj')" Condition="$(IncludeTests)">
       <!-- Disable parallel build for test modules -->
       <BuildInParallel>false</BuildInParallel>
index c291b7f86325f2fbccca4c2a885497005db20707..14aac0b0dc84b67914d3a7c66c704f18a013a12e 100644 (file)
@@ -168,8 +168,9 @@ xxlimited
     builds an example module that makes use of the PEP 384 Stable ABI,
     see Modules\xxlimited.c
 xxlimited_35
-    ditto for testing the Python 3.5 stable ABI, see
-    Modules\xxlimited_35.c
+xxlimited_3_13
+    ditto for testing older Limited API, see
+    Modules\xxlimited_*.c
 
 The following sub-projects are for individual modules of the standard
 library which are implemented in C; each one builds a DLL (renamed to
diff --git a/PCbuild/xxlimited_3_13.vcxproj b/PCbuild/xxlimited_3_13.vcxproj
new file mode 100644 (file)
index 0000000..7a9760f
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|ARM">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|ARM64">
+      <Configuration>Debug</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGInstrument|ARM">
+      <Configuration>PGInstrument</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGInstrument|ARM64">
+      <Configuration>PGInstrument</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGInstrument|Win32">
+      <Configuration>PGInstrument</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGInstrument|x64">
+      <Configuration>PGInstrument</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGUpdate|ARM">
+      <Configuration>PGUpdate</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGUpdate|ARM64">
+      <Configuration>PGUpdate</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGUpdate|Win32">
+      <Configuration>PGUpdate</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="PGUpdate|x64">
+      <Configuration>PGUpdate</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM">
+      <Configuration>Release</Configuration>
+      <Platform>ARM</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|ARM64">
+      <Configuration>Release</Configuration>
+      <Platform>ARM64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{fb868ea7-f93a-4d9b-be78-ca4e9ba14fff}</ProjectGuid>
+    <RootNamespace>xxlimited_3_13</RootNamespace>
+    <Keyword>Win32Proj</Keyword>
+  </PropertyGroup>
+  <Import Project="python.props" />
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <CharacterSet>NotSet</CharacterSet>
+    <SupportPGO>false</SupportPGO>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <PropertyGroup>
+    <TargetExt>$(PyStdlibPydExt)</TargetExt>
+  </PropertyGroup>
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="pyproject.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup>
+    <_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
+  </PropertyGroup>
+  <ItemDefinitionGroup>
+    <Link>
+      <AdditionalDependencies>wsock32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\Modules\xxlimited_3_13.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="python3dll.vcxproj">
+      <Project>{885d4898-d08d-4091-9c40-c700cfe3fc5a}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
diff --git a/PCbuild/xxlimited_3_13.vcxproj.filters b/PCbuild/xxlimited_3_13.vcxproj.filters
new file mode 100644 (file)
index 0000000..3dfb780
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{5be27194-6530-452d-8d86-3767b991fa83}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\Modules\xxlimited_3_13.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
index bda72539640611861b512288d03f9827ee427a43..f8828a56b4c7da73d89256096ed5f8f4cb341e72 100644 (file)
@@ -42,6 +42,7 @@ IGNORE = {
     'test',
     'xxlimited',
     'xxlimited_35',
+    'xxlimited_3_13',
     'xxsubtype',
 }
 
index 4a55a1a24ee1bedd6af82cec7c05ec04eb10e26f..92134bc1321e1b546d568317b913375c72c101c3 100644 (file)
@@ -11,6 +11,7 @@ FILES_WITHOUT_INTERNAL_CAPI = frozenset((
     '_testclinic_limited.c',
     'xxlimited.c',
     'xxlimited_35.c',
+    'xxlimited_3_13.c',
 ))
 
 # C files in the fhe following directories must not be built with
index 43ed552fcf75d9025af4acce33038a25699617d7..404a81af11e39ffdf9b72eab2f1d2b5c13015bb0 100644 (file)
@@ -77,6 +77,7 @@ _OTHER_SUPPORTED_TYPES = {
     'PyStructSequence_Field[]',
     'PyStructSequence_Desc',
     'PyABIInfo',
+    'PySlot[]',
 }
 
 # XXX We should normalize all cases to a single name,
index da4e437849f79061843b3f9cbe47178760bff767..f3489d0fae09b7aabe788097da8aba556f31e80a 100755 (executable)
--- a/configure
+++ b/configure
@@ -647,6 +647,8 @@ MODULE_BLOCK
 JIT_SHIM_BUILD_O
 JIT_SHIM_O
 JIT_STENCILS_H
+MODULE_XXLIMITED_3_13_FALSE
+MODULE_XXLIMITED_3_13_TRUE
 MODULE_XXLIMITED_35_FALSE
 MODULE_XXLIMITED_35_TRUE
 MODULE_XXLIMITED_FALSE
@@ -31879,6 +31881,7 @@ case $ac_sys_system in #(
     py_cv_module_termios=n/a
     py_cv_module_xxlimited=n/a
     py_cv_module_xxlimited_35=n/a
+    py_cv_module_xxlimited_3_13=n/a
     py_cv_module_=n/a
 
    ;; #(
 printf "%s\n" "$py_cv_module_xxlimited_35" >&6; }
 
 
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module xxlimited_3_13" >&5
+printf %s "checking for stdlib extension module xxlimited_3_13... " >&6; }
+        if test "$py_cv_module_xxlimited_3_13" != "n/a"
+then :
+
+    if test "$TEST_MODULES" = yes
+then :
+  if test "$ac_cv_func_dlopen" = yes
+then :
+  py_cv_module_xxlimited_3_13=yes
+else case e in #(
+  e) py_cv_module_xxlimited_3_13=missing ;;
+esac
+fi
+else case e in #(
+  e) py_cv_module_xxlimited_3_13=disabled ;;
+esac
+fi
+
+fi
+  as_fn_append MODULE_BLOCK "MODULE_XXLIMITED_3_13_STATE=$py_cv_module_xxlimited_3_13$as_nl"
+  if test "x$py_cv_module_xxlimited_3_13" = xyes
+then :
+
+
+
+
+fi
+   if test "$py_cv_module_xxlimited_3_13" = yes; then
+  MODULE_XXLIMITED_3_13_TRUE=
+  MODULE_XXLIMITED_3_13_FALSE='#'
+else
+  MODULE_XXLIMITED_3_13_TRUE='#'
+  MODULE_XXLIMITED_3_13_FALSE=
+fi
+
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module_xxlimited_3_13" >&5
+printf "%s\n" "$py_cv_module_xxlimited_3_13" >&6; }
+
+
 # Determine JIT stencils header files based on target platform
 JIT_STENCILS_H=""
 JIT_SHIM_O=""
@@ -35448,6 +35491,10 @@ if test -z "${MODULE_XXLIMITED_35_TRUE}" && test -z "${MODULE_XXLIMITED_35_FALSE
   as_fn_error $? "conditional \"MODULE_XXLIMITED_35\" was never defined.
 Usually this means the macro was only invoked conditionally." "$LINENO" 5
 fi
+if test -z "${MODULE_XXLIMITED_3_13_TRUE}" && test -z "${MODULE_XXLIMITED_3_13_FALSE}"; then
+  as_fn_error $? "conditional \"MODULE_XXLIMITED_3_13\" was never defined.
+Usually this means the macro was only invoked conditionally." "$LINENO" 5
+fi
 
 : "${CONFIG_STATUS=./config.status}"
 ac_write_fail=0
index 9b5a865c6beb945a0786e112b21c95bca7876e8a..1bb845835ff68eae0b00e4984eb093d6613c0eba 100644 (file)
@@ -7962,6 +7962,7 @@ AS_CASE([$ac_sys_system],
       [termios],
       [xxlimited],
       [xxlimited_35],
+      [xxlimited_3_13],
     )
   ],
   [PY_STDLIB_MOD_SET_NA([_scproxy])]
@@ -8349,6 +8350,7 @@ dnl Limited API template modules.
 dnl Emscripten does not support shared libraries yet.
 PY_STDLIB_MOD([xxlimited], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
 PY_STDLIB_MOD([xxlimited_35], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
+PY_STDLIB_MOD([xxlimited_3_13], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
 
 # Determine JIT stencils header files based on target platform
 JIT_STENCILS_H=""