From 8e3271d3101540f8662745730a1a04635def502a Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 24 Aug 2026 23:42:12 -0600 Subject: [PATCH 1/3] gh-145177: Upgrade to Emscripten 6.0.8 (#156334) --- Platforms/emscripten/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Platforms/emscripten/config.toml b/Platforms/emscripten/config.toml index 285e17a997b1e7..e1ffd6cd04d60b 100644 --- a/Platforms/emscripten/config.toml +++ b/Platforms/emscripten/config.toml @@ -1,7 +1,7 @@ # Any data that can vary between Python versions is to be kept in this file. # This allows for blanket copying of the Emscripten build code between supported # Python versions. -emscripten-version = "6.0.6" +emscripten-version = "6.0.8" node-version = "24" test-args = [ "-m", "test", From fe8ace30aeeb5991c59e2dcb65d8c32f0c262d26 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Tue, 25 Aug 2026 11:30:58 +0300 Subject: [PATCH 2/3] gh-156207: Fix curses Textbox.gather() for double-width characters (GH-156208) gather() read the window cell by cell, so a double-width character, which occupies two cells, was returned twice. Read the whole line with in_wstr(), which returns every character once. --- Lib/curses/textpad.py | 18 ++++++++++-------- Lib/test/test_curses.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Lib/curses/textpad.py b/Lib/curses/textpad.py index f6dfc990901d99..31ad37539ca4a4 100644 --- a/Lib/curses/textpad.py +++ b/Lib/curses/textpad.py @@ -200,14 +200,16 @@ def gather(self): result = "" self._update_max_yx() for y in range(self.maxy+1): - self.win.move(y, 0) - stop = self._end_of_line(y) - if stop == 0 and self.stripspaces: - continue - for x in range(self.maxx+1): - if self.stripspaces and x > stop: - break - result = result + str(self.win.in_wch(y, x)) + # The whole line: in_wstr() reads a double-width character once, + # skipping the continuation cell that holds its other half. + line = self.win.in_wstr(y, 0) + if self.stripspaces: + stripped = line.rstrip(' ') + if not stripped: + continue + # Keep the blank the cursor rests on past the last character. + line = stripped + ' ' if len(stripped) < len(line) else stripped + result = result + line if self.maxy > 0: result = result + "\n" return result diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 630de544a457f4..13b024941f739b 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -2694,6 +2694,23 @@ def test_textbox_combining(self): box.do_command(ch) self.assertEqual(box.gather(), text + ' ') + @requires_wide_build + def test_textbox_double_width(self): + # A double-width (East Asian) character occupies two cells. gather() + # reads a whole line at a time so that the second cell, which holds + # the same character, is not reported as another one. + text = '你好' + if not self._encodable(text): + self.skipTest('the locale cannot encode %r' % text) + box, win = self._make_textbox(1, 12) + for ch in text: + box.do_command(ch) + self.assertEqual(box.gather(), text + ' ') + box, win = self._make_textbox(1, 12, stripspaces=False) + for ch in text: + box.do_command(ch) + self.assertEqual(box.gather(), text + ' ' * 8) + def test_textbox_edit_wide(self): # edit() reads characters through get_wch(). Each character is pushed # with unget_wch(), which on a narrow build requires it to encode to a From a728080492e6cdcffebbfe11e520a1cf6e7aaee1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 25 Aug 2026 12:22:46 +0300 Subject: [PATCH 3/3] gh-156348: Fix the value of curses.ERR (GH-156349) Setting the module constants with an unsigned conversion, needed for the chtype constants that can set bits beyond a 32-bit long, turned ERR from -1 into 18446744073709551615. Set ERR and OK with a signed conversion. --- Lib/test/test_curses.py | 5 +++++ Modules/_cursesmodule.c | 29 ++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 13b024941f739b..389cd043d6c0f3 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -2984,6 +2984,11 @@ def test_has_extended_color_support(self): r = curses.has_extended_color_support() self.assertIsInstance(r, bool) + def test_err_and_ok(self): + # ERR is negative; it is not a chtype constant. + self.assertEqual(curses.ERR, -1) + self.assertEqual(curses.OK, 0) + def test_type_names(self): # The curses types report their public module rather than the # underscore extension that implements them. diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index bd13022261c20e..2ff15dd31d2180 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -6657,11 +6657,10 @@ curses_init_dict(PyObject *module) } /* This was moved from initcurses() because it core dumped on SGI, where they're not defined until you've called initscr() */ - /* Use long long, not long: a chtype constant (the A_* attributes, ACS_* - and key codes) can set bits beyond a 32-bit long, which is what long is - on LLP64 platforms such as Windows -- A_DIM (0x80000000) would otherwise - be sign-extended to a negative number. long long is at least 64 bits - everywhere and still represents the negative ERR (-1). */ + /* Use unsigned long long, not long: a chtype constant (the A_* attributes, + ACS_* and key codes) can set bits beyond a 32-bit long, which is what + long is on LLP64 platforms such as Windows -- A_DIM (0x80000000) would + otherwise be sign-extended to a negative number. */ #define SetDictInt(NAME, VALUE) \ do { \ PyObject *value = PyLong_FromUnsignedLongLong((unsigned long long)(VALUE)); \ @@ -9419,8 +9418,24 @@ cursesmodule_exec(PyObject *module) } \ } while (0) - SetDictInt("ERR", ERR); - SetDictInt("OK", OK); + /* ERR is -1, so it needs a signed conversion, unlike the chtype + constants below. */ +#define SetDictSignedInt(NAME, VALUE) \ + do { \ + PyObject *value = PyLong_FromLongLong((long long)(VALUE)); \ + if (value == NULL) { \ + return -1; \ + } \ + int rc = PyDict_SetItemString(module_dict, (NAME), value); \ + Py_DECREF(value); \ + if (rc < 0) { \ + return -1; \ + } \ + } while (0) + + SetDictSignedInt("ERR", ERR); + SetDictSignedInt("OK", OK); +#undef SetDictSignedInt /* Here are some attributes you can add to chars to print */