[3.13] gh-155218: Generate the option group flags in a stable order (GH-155219) (GH-155225)

serhiy-storchaka <[email protected]> Wed, 05 Aug 2026 05:27:35 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/965c21d9f7aff3ca577d50e7f9b90118d2b25661
commit: 965c21d9f7aff3ca577d50e7f9b90118d2b25661
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-05T09:27:21Z
summary:

[3.13] gh-155218: Generate the option group flags in a stable order (GH-155219) (GH-155225)

They were emitted in the iteration order of a set of group identifiers.
The identifiers of the groups before the required parameters are small
negative integers, -1 and -2 have the same hash, so their order depended
on the size of Py_hash_t, and the code generated on a 32-bit platform
differed from the checked in one.

They are now emitted in the order of the parameters.
(cherry picked from commit 7aec160315a6316f7391abda10d4b6c57032d105)

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Tools-Demos/2026-08-05-14-02-55.gh-issue-155218.Nq4xZv.rst
M Modules/clinic/_cursesmodule.c.h
M Tools/clinic/libclinic/clanguage.py

diff --git a/Misc/NEWS.d/next/Tools-Demos/2026-08-05-14-02-55.gh-issue-155218.Nq4xZv.rst b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-14-02-55.gh-issue-155218.Nq4xZv.rst
new file mode 100644
index 000000000000000..909efc558693c0a
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-14-02-55.gh-issue-155218.Nq4xZv.rst
@@ -0,0 +1,2 @@
+Fix Argument Clinic generating the flags of the optional groups in
+different order on 32-bit and 64-bit platforms.
diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h
index 6f7fe9c3708136f..84069010cdcf224 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -67,8 +67,8 @@ _curses_window_addch(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOl:addch", &y, &x, &ch, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.addch requires 1 to 4 arguments");
@@ -139,8 +139,8 @@ _curses_window_addstr(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOl:addstr", &y, &x, &str, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.addstr requires 1 to 4 arguments");
@@ -214,8 +214,8 @@ _curses_window_addnstr(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOil:addnstr", &y, &x, &str, &n, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.addnstr requires 2 to 5 arguments");
@@ -959,8 +959,8 @@ _curses_window_hline(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOil:hline", &y, &x, &ch, &n, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.hline requires 2 to 5 arguments");
@@ -1029,8 +1029,8 @@ _curses_window_insch(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOl:insch", &y, &x, &ch, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.insch requires 1 to 4 arguments");
@@ -1153,8 +1153,8 @@ _curses_window_insstr(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOl:insstr", &y, &x, &str, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.insstr requires 1 to 4 arguments");
@@ -1230,8 +1230,8 @@ _curses_window_insnstr(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOil:insnstr", &y, &x, &str, &n, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.insnstr requires 2 to 5 arguments");
@@ -1828,8 +1828,8 @@ _curses_window_vline(PyCursesWindowObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOil:vline", &y, &x, &ch, &n, &attr)) {
                 goto exit;
             }
-            group_right_1 = 1;
             group_left_1 = 1;
+            group_right_1 = 1;
             break;
         default:
             PyErr_SetString(PyExc_TypeError, "_curses.window.vline requires 2 to 5 arguments");
@@ -4409,4 +4409,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
 #ifndef _CURSES_USE_DEFAULT_COLORS_METHODDEF
     #define _CURSES_USE_DEFAULT_COLORS_METHODDEF
 #endif /* !defined(_CURSES_USE_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=80ed45ba69ca7654 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=298ec74288ea2852 input=a9049054013a1b77]*/
diff --git a/Tools/clinic/libclinic/clanguage.py b/Tools/clinic/libclinic/clanguage.py
index fa52d18289c8bec..4c5357f098cb98f 100644
--- a/Tools/clinic/libclinic/clanguage.py
+++ b/Tools/clinic/libclinic/clanguage.py
@@ -320,7 +320,9 @@ def render_option_group_parsing(
 """)
                 continue
 
-            group_ids = {p.group for p in subset}  # eliminate duplicates
+            # A set would eliminate duplicates too, but the iteration
+            # order of small negative integers depends on the platform.
+            group_ids = dict.fromkeys(p.group for p in subset)
             d: dict[str, str | int] = {}
             d['count'] = count
             d['name'] = f.name
@@ -331,7 +333,7 @@ def render_option_group_parsing(
                 p.converter.parse_argument(parse_arguments)
             d['parse_arguments'] = ", ".join(parse_arguments)
 
-            group_ids.discard(0)
+            group_ids.pop(0, None)
             lines = "\n".join([
                 self.group_to_variable_name(g) + " = 1;"
                 for g in group_ids

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]