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

serhiy-storchaka <[email protected]> Wed, 05 Aug 2026 04:31:28 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/7aec160315a6316f7391abda10d4b6c57032d105
commit: 7aec160315a6316f7391abda10d4b6c57032d105
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-05T08:31:11Z
summary:

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

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.

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 dfd589ba45089e7..58087416796f8fc 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -247,8 +247,8 @@ _curses_window_addch(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOO&:addch", &y, &x, &ch, attr_converter, &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");
@@ -319,8 +319,8 @@ _curses_window_addstr(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOO&:addstr", &y, &x, &str, attr_converter, &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");
@@ -394,8 +394,8 @@ _curses_window_addnstr(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOiO&:addnstr", &y, &x, &str, &n, attr_converter, &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");
@@ -1451,8 +1451,8 @@ _curses_window_hline(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOiO&:hline", &y, &x, &ch, &n, attr_converter, &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");
@@ -1521,8 +1521,8 @@ _curses_window_insch(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOO&:insch", &y, &x, &ch, attr_converter, &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");
@@ -1640,8 +1640,8 @@ _curses_window_insstr(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOO&:insstr", &y, &x, &str, attr_converter, &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");
@@ -1717,8 +1717,8 @@ _curses_window_insnstr(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOiO&:insnstr", &y, &x, &str, &n, attr_converter, &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");
@@ -2328,8 +2328,8 @@ _curses_window_vline(PyObject *self, PyObject *args)
             if (!PyArg_ParseTuple(args, "iiOiO&:vline", &y, &x, &ch, &n, attr_converter, &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");
@@ -6234,4 +6234,4 @@ _curses_has_extended_color_support(PyObject *module, PyObject *Py_UNUSED(ignored
 #ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
     #define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
 #endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=cb5525c88ae5c440 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=c1f85ec415c303bf input=a9049054013a1b77]*/
diff --git a/Tools/clinic/libclinic/clanguage.py b/Tools/clinic/libclinic/clanguage.py
index a76fddb7602001e..3ee06307441fac4 100644
--- a/Tools/clinic/libclinic/clanguage.py
+++ b/Tools/clinic/libclinic/clanguage.py
@@ -341,7 +341,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
@@ -356,7 +358,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]