]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-145247: Implement _PyTuple_FromPair() (#145325)
authorSergey Miryanov <sergey.miryanov@gmail.com>
Tue, 10 Mar 2026 10:44:20 +0000 (15:44 +0500)
committerGitHub <noreply@github.com>
Tue, 10 Mar 2026 10:44:20 +0000 (11:44 +0100)
Implement _PyTuple_FromPair() and _PyTuple_FromPairSteal().

Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Include/internal/pycore_tuple.h
Lib/test/test_capi/test_tuple.py
Modules/Setup.stdlib.in
Modules/_testinternalcapi.c
Modules/_testinternalcapi/parts.h
Modules/_testinternalcapi/tuple.c [new file with mode: 0644]
Objects/tupleobject.c
PCbuild/_testinternalcapi.vcxproj
PCbuild/_testinternalcapi.vcxproj.filters

index 00562bef769920303a5e7c5a45fdfaf018a8bc53..b3fa28f5bf21d58d771188abbad1fcc3c750a4c0 100644 (file)
@@ -27,6 +27,9 @@ PyAPI_FUNC(PyObject *)_PyTuple_FromStackRefStealOnSuccess(const union _PyStackRe
 PyAPI_FUNC(PyObject *)_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
 PyAPI_FUNC(PyObject *) _PyTuple_BinarySlice(PyObject *, PyObject *, PyObject *);
 
+PyAPI_FUNC(PyObject *) _PyTuple_FromPair(PyObject *, PyObject *);
+PyAPI_FUNC(PyObject *) _PyTuple_FromPairSteal(PyObject *, PyObject *);
+
 typedef struct {
     PyObject_HEAD
     Py_ssize_t it_index;
index d6669d7802c5b84c7a9ac4b565e9e0ac6b98e0ca..0c27e81168ff770858f94808d95b0b042e7e11cc 100644 (file)
@@ -1,9 +1,11 @@
 import unittest
 import gc
+from sys import getrefcount
 from test.support import import_helper
 
 _testcapi = import_helper.import_module('_testcapi')
 _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+_testinternalcapi = import_helper.import_module('_testinternalcapi')
 
 NULL = None
 PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN
@@ -118,6 +120,41 @@ class CAPITest(unittest.TestCase):
         # CRASHES pack(1, NULL)
         # CRASHES pack(2, [1])
 
+    def check_tuple_from_pair(self, from_pair):
+        self.assertEqual(type(from_pair(1, 2)), tuple)
+        self.assertEqual(from_pair(1, 145325), (1, 145325))
+        self.assertEqual(from_pair(None, None), (None, None))
+        self.assertEqual(from_pair(True, False), (True, False))
+
+        # user class supports gc
+        class Temp:
+            pass
+        temp = Temp()
+        temp_rc = getrefcount(temp)
+        self.assertEqual(from_pair(temp, temp), (temp, temp))
+        self.assertEqual(getrefcount(temp), temp_rc)
+
+        self._not_tracked(from_pair(1, 2))
+        self._not_tracked(from_pair(None, None))
+        self._not_tracked(from_pair(True, False))
+        self._tracked(from_pair(temp, (1, 2)))
+        self._tracked(from_pair(temp, 1))
+        self._tracked(from_pair([], {}))
+
+        self.assertRaises(TypeError, from_pair, 1, 2, 3)
+        self.assertRaises(TypeError, from_pair, 1)
+        self.assertRaises(TypeError, from_pair)
+
+    def test_tuple_from_pair(self):
+        # Test _PyTuple_FromPair()
+        from_pair = _testinternalcapi.tuple_from_pair
+        self.check_tuple_from_pair(from_pair)
+
+    def test_tuple_from_pair_steal(self):
+        # Test _PyTuple_FromPairSteal()
+        from_pair = _testinternalcapi.tuple_from_pair_steal
+        self.check_tuple_from_pair(from_pair)
+
     def test_tuple_size(self):
         # Test PyTuple_Size()
         size = _testlimitedcapi.tuple_size
index 39be41d9d2a4260f1dc143d7b1f59370e4553a53..0d520684c795d60f0ff9e0c0269a43a25ee90c10 100644 (file)
 @MODULE_XXSUBTYPE_TRUE@xxsubtype xxsubtype.c
 @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
-@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c
+@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c
 @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c
 @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c
 @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
index b6ed0b8902354e4ee1542773c9d550c8931d94d9..aa5911ef2fb449ecd9b4d4eb7c6a5803a74e8c9e 100644 (file)
@@ -2987,6 +2987,9 @@ module_exec(PyObject *module)
     if (_PyTestInternalCapi_Init_CriticalSection(module) < 0) {
         return 1;
     }
+    if (_PyTestInternalCapi_Init_Tuple(module) < 0) {
+        return 1;
+    }
 
     Py_ssize_t sizeof_gc_head = 0;
 #ifndef Py_GIL_DISABLED
index 03557d5bf5957fca32d860d5e2217c38cc063ab4..81f536c3babb18cd9c543b4d24e7008d4aa0a6ab 100644 (file)
@@ -15,5 +15,6 @@ int _PyTestInternalCapi_Init_PyTime(PyObject *module);
 int _PyTestInternalCapi_Init_Set(PyObject *module);
 int _PyTestInternalCapi_Init_Complex(PyObject *module);
 int _PyTestInternalCapi_Init_CriticalSection(PyObject *module);
+int _PyTestInternalCapi_Init_Tuple(PyObject *module);
 
 #endif // Py_TESTINTERNALCAPI_PARTS_H
diff --git a/Modules/_testinternalcapi/tuple.c b/Modules/_testinternalcapi/tuple.c
new file mode 100644 (file)
index 0000000..c12ee32
--- /dev/null
@@ -0,0 +1,39 @@
+#include "parts.h"
+
+#include "pycore_tuple.h"
+
+
+static PyObject *
+tuple_from_pair(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *first, *second;
+    if (!PyArg_ParseTuple(args, "OO", &first, &second)) {
+        return NULL;
+    }
+
+    return _PyTuple_FromPair(first, second);
+}
+
+static PyObject *
+tuple_from_pair_steal(PyObject *Py_UNUSED(module), PyObject *args)
+{
+    PyObject *first, *second;
+    if (!PyArg_ParseTuple(args, "OO", &first, &second)) {
+        return NULL;
+    }
+
+    return _PyTuple_FromPairSteal(Py_NewRef(first), Py_NewRef(second));
+}
+
+
+static PyMethodDef test_methods[] = {
+    {"tuple_from_pair", tuple_from_pair, METH_VARARGS},
+    {"tuple_from_pair_steal", tuple_from_pair_steal, METH_VARARGS},
+    {NULL},
+};
+
+int
+_PyTestInternalCapi_Init_Tuple(PyObject *m)
+{
+    return PyModule_AddFunctions(m, test_methods);
+}
index 3c68955495d566cc4a7e913aa1453d38484d84fd..01afa53e15cd5dda3f9881aa68ccf732006f736c 100644 (file)
@@ -202,6 +202,35 @@ PyTuple_Pack(Py_ssize_t n, ...)
     return (PyObject *)result;
 }
 
+PyObject *
+_PyTuple_FromPair(PyObject *first, PyObject *second)
+{
+    assert(first != NULL);
+    assert(second != NULL);
+
+    return _PyTuple_FromPairSteal(Py_NewRef(first), Py_NewRef(second));
+}
+
+PyObject *
+_PyTuple_FromPairSteal(PyObject *first, PyObject *second)
+{
+    assert(first != NULL);
+    assert(second != NULL);
+
+    PyTupleObject *op = tuple_alloc(2);
+    if (op == NULL) {
+        Py_DECREF(first);
+        Py_DECREF(second);
+        return NULL;
+    }
+    PyObject **items = op->ob_item;
+    items[0] = first;
+    items[1] = second;
+    if (maybe_tracked(first) || maybe_tracked(second)) {
+        _PyObject_GC_TRACK(op);
+    }
+    return (PyObject *)op;
+}
 
 /* Methods */
 
index 3818e6d3f7bbd24e2b0880af78f79e95c7058d16..f3e423fa04668ecc184a2a12b17f6516de32bfff 100644 (file)
     <ClCompile Include="..\Modules\_testinternalcapi\set.c" />
     <ClCompile Include="..\Modules\_testinternalcapi\complex.c" />
     <ClCompile Include="..\Modules\_testinternalcapi\interpreter.c" />
+    <ClCompile Include="..\Modules\_testinternalcapi\tuple.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
index 012d709bd1ce5ddc606b611a04e0bf1e2bf57145..7ab242c2c230b67e20a33d87cf6ed31c837d1ea4 100644 (file)
@@ -27,6 +27,9 @@
     <ClCompile Include="..\Modules\_testinternalcapi\complex.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testinternalcapi\tuple.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">