From 20734734802d7505823c8f2eeafd003d5b7fe4f1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 22 Sep 2026 21:51:55 +0300 Subject: [PATCH 1/4] gh-85560: Fix IDLE paren matching with "else" or "yield" at the start of a continuation line (#157693) `pyparse._synchre` took any line starting with `else` or `yield` for the start of a statement, so the parenthesis containing a conditional expression or a yield expression continued on such a line (`(1 if x\n else 0)`, `(\n yield x)`) was not found by Show Surrounding Parens. An `else` statement is always followed by a colon, a conditional expression never, so `else` now counts as a statement start only when followed by `:`. A `yield` statement cannot be told from a yield expression, so `yield` is removed from the list; it only served to shorten the parsed text. On the other hand, `with`, `del`, `global`, `nonlocal`, `pass` and `finally`, which cannot start a continuation line, are added. --- Lib/idlelib/idle_test/test_pyparse.py | 45 +++++++++++++++---- Lib/idlelib/pyparse.py | 9 +++- ...09-17-18-00-00.gh-issue-85560.elseexpr.rst | 3 ++ 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-18-00-00.gh-issue-85560.elseexpr.rst diff --git a/Lib/idlelib/idle_test/test_pyparse.py b/Lib/idlelib/idle_test/test_pyparse.py index 384db566ac76cd..601e8a93fdfa85 100644 --- a/Lib/idlelib/idle_test/test_pyparse.py +++ b/Lib/idlelib/idle_test/test_pyparse.py @@ -78,7 +78,7 @@ def char_in_string_false(index): return False ' b=True):\n' ' pass\n' ) - pos0, pos = 33, 42 # Start of 'class...', ' def' lines. + pos0, pos, pos1 = 33, 42, 94 # Start of 'class', 'def', 'pass' lines. # Passing no value or non-callable should fail (issue 32989). with self.assertRaises(TypeError): @@ -91,8 +91,8 @@ def char_in_string_false(index): return False self.assertIsNone(start(is_char_in_string=lambda index: True)) # Make all text look like it's not in a string. This means that it - # found a good start position. - eq(start(char_in_string_false), pos) + # found a good start position: the last statement. + eq(start(char_in_string_false), pos1) # If the beginning of the def line is not in a string, then it # returns that as the index. @@ -100,9 +100,9 @@ def char_in_string_false(index): return False # If the beginning of the def line is in a string, then it # looks for a previous index. eq(start(is_char_in_string=lambda index: index >= pos), pos0) - # If everything before the 'def' is in a string, then returns None. - # The non-continuation def line returns 44 (see below). - eq(start(is_char_in_string=lambda index: index < pos), None) + # If everything before the 'def' is in a string, then returns + # the start of the 'pass' line. + eq(start(is_char_in_string=lambda index: index < pos), pos1) # Code without extra line break in def line - mostly returns the same # values. @@ -111,12 +111,41 @@ def char_in_string_false(index): return False ' def __init__(self, a, b=True):\n' ' pass\n' ) # Does not affect class, def positions. - eq(start(char_in_string_false), pos) + pos1 = 77 # Start of 'pass' line. + eq(start(char_in_string_false), pos1) eq(start(is_char_in_string=lambda index: index > pos), pos) eq(start(is_char_in_string=lambda index: index >= pos), pos0) # When the def line isn't split, this returns which doesn't match the # split line test. - eq(start(is_char_in_string=lambda index: index < pos), pos) + eq(start(is_char_in_string=lambda index: index < pos), pos1) + + # gh-85560: 'else' of a conditional expression at the start of + # a continuation line does not start a statement. + setcode('def f():\n' + ' return (1 if x\n' + ' else 0)\n') + eq(start(char_in_string_false), 9) # Start of 'return' line. + setcode('if x:\n' + ' pass\n' + 'else:\n' + ' x = 1\n') + eq(start(char_in_string_false), 15) + setcode('if x:\n' + ' pass\n' + 'else : # comment\n' + ' x = 1\n') + eq(start(char_in_string_false), 15) + # A yield expression can start a continuation line too. + setcode('def f():\n' + ' x = (\n' + ' yield y)\n') + eq(start(char_in_string_false), 0) + # Other statements which cannot start a continuation line. + for stmt in ('with x:', 'del x', 'global x', 'nonlocal x', 'pass', + 'finally:'): + with self.subTest(stmt=stmt): + setcode(f'if x:\n pass\n{stmt}\n') + eq(start(char_in_string_false), 15) def test_set_lo(self): code = ( diff --git a/Lib/idlelib/pyparse.py b/Lib/idlelib/pyparse.py index 8545c63e1435d4..2271b787f341d6 100644 --- a/Lib/idlelib/pyparse.py +++ b/Lib/idlelib/pyparse.py @@ -22,7 +22,7 @@ ^ [ \t]* (?: while - | else + | else (?= [ \t]* : ) # not "else" of a conditional expression | def | return | assert @@ -34,7 +34,12 @@ | except | raise | import - | yield + | with + | del + | global + | nonlocal + | pass + | finally ) \b """, re.VERBOSE | re.MULTILINE).search diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-18-00-00.gh-issue-85560.elseexpr.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-18-00-00.gh-issue-85560.elseexpr.rst new file mode 100644 index 00000000000000..9e20622bb535b0 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-18-00-00.gh-issue-85560.elseexpr.rst @@ -0,0 +1,3 @@ +Fix IDLE failing to find the opening parenthesis when a continuation line +inside the parentheses starts with ``else`` of a conditional expression or +with ``yield``. From f67c51fb6077f759b048727cf367fdb2c88d3a19 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 22 Sep 2026 22:20:25 +0300 Subject: [PATCH 2/4] gh-75487: Do not save a truncated number when an int entry is blanked in IDLE Settings (#157680) The value is recorded on every keystroke, so blanking "80" with the Backspace key left "8" to be saved. Blanking an entry now forgets the recorded value, so that the saved value is kept. --- Lib/idlelib/configdialog.py | 7 +++++-- Lib/idlelib/idle_test/test_configdialog.py | 4 ++++ .../IDLE/2026-09-17-17-00-00.gh-issue-75487.blankint.rst | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-17-00-00.gh-issue-75487.blankint.rst diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index a5d1aaeb5a946f..02133ce022d79e 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -2285,10 +2285,13 @@ def make_callback(var, config): def default_callback(*params): "Add config values to changes instance." value = var.get() - # A blanked int entry is an empty string; do not save it as an - # invalid config value (gh-83653). if value != '': changes.add_option(*config, value) + else: + # A blanked int entry: do not save an invalid value, and + # forget the value recorded while editing (gh-75487). + config_type, section, item = config + changes[config_type].get(section, {}).pop(item, None) return default_callback def attach(self): diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index 367c63dc01126a..5e209287a52d97 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -1711,6 +1711,10 @@ def test_make_callback(self): sv.set('5') cb() self.assertEqual(changes['main']['section']['option'], '5') + # gh-75487: blanking the entry forgets the value recorded before. + sv.set('') + cb() + self.assertNotIn('option', changes['main']['section']) changes.clear() def test_attach_detach(self): diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-17-00-00.gh-issue-75487.blankint.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-17-00-00.gh-issue-75487.blankint.rst new file mode 100644 index 00000000000000..5ae2d360f5c6a9 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-17-00-00.gh-issue-75487.blankint.rst @@ -0,0 +1,2 @@ +Fix saving a truncated number when an integer entry in the IDLE Settings +dialog is blanked with the Backspace key. From 6f64920479e37d30c8a5c59f07c7b47712a8ccc7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 22 Sep 2026 22:17:16 +0200 Subject: [PATCH 3/4] gh-157695: Fix acospi() detection in configure.ac (#157948) Include to check for acospi() and similar functions. --- configure | 14 +++++++------- configure.ac | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/configure b/configure index 84f2030049ac31..e5d3f0091058ac 100755 --- a/configure +++ b/configure @@ -27593,7 +27593,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27635,7 +27635,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27677,7 +27677,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27719,7 +27719,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27761,7 +27761,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27803,7 +27803,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { @@ -27845,7 +27845,7 @@ then : else case e in #( e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - +#include int main (void) { diff --git a/configure.ac b/configure.ac index bc94908213d2c7..82a623fedb8192 100644 --- a/configure.ac +++ b/configure.ac @@ -6555,13 +6555,13 @@ AC_CHECK_FUNCS( [AC_MSG_ERROR([Python requires C99 compatible libm])] ) -PY_CHECK_FUNC_PRIVATE([acospi]) -PY_CHECK_FUNC_PRIVATE([asinpi]) -PY_CHECK_FUNC_PRIVATE([atanpi]) -PY_CHECK_FUNC_PRIVATE([atan2pi]) -PY_CHECK_FUNC_PRIVATE([cospi]) -PY_CHECK_FUNC_PRIVATE([sinpi]) -PY_CHECK_FUNC_PRIVATE([tanpi]) +PY_CHECK_FUNC_PRIVATE([acospi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([asinpi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([atanpi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([atan2pi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([cospi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([sinpi], [@%:@include ]) +PY_CHECK_FUNC_PRIVATE([tanpi], [@%:@include ]) LIBS=$LIBS_SAVE dnl For multiprocessing module, check that sem_open From 6757482c25d4fa308d3f3baaa57b6db1a8c720f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Wed, 23 Sep 2026 04:45:00 +0800 Subject: [PATCH 4/4] gh-151049: Add 2001:1::3/128 (RFC 9665) to ipaddress IPv6 global exceptions (#151050) IANA's IPv6 Special-Purpose Address Registry (2024-04) lists 2001:1::3/128 ("DNS-SD Service Registration Protocol Anycast", RFC 9665) as globally reachable, like 2001:1::1/128 and 2001:1::2/128. It was missing from _IPv6Constants._private_networks_exceptions, so is_global returned False for it. Add the exception and extend testReservedIpv6. --- Lib/ipaddress.py | 2 ++ Lib/test/test_ipaddress.py | 4 ++++ .../Library/2026-06-07-23-30-34.gh-issue-151049.mBlToO.rst | 4 ++++ 3 files changed, 10 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-07-23-30-34.gh-issue-151049.mBlToO.rst diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py index f160cc6a7ce900..8ac2a537c46dee 100644 --- a/Lib/ipaddress.py +++ b/Lib/ipaddress.py @@ -2458,6 +2458,8 @@ class _IPv6Constants: _private_networks_exceptions = [ IPv6Network('2001:1::1/128'), IPv6Network('2001:1::2/128'), + # RFC 9665: https://www.rfc-editor.org/rfc/rfc9665.html + IPv6Network('2001:1::3/128'), IPv6Network('2001:3::/32'), IPv6Network('2001:4:112::/48'), IPv6Network('2001:20::/28'), diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py index d6d0d8220449bd..600e1f19a9cdc2 100644 --- a/Lib/test/test_ipaddress.py +++ b/Lib/test/test_ipaddress.py @@ -2535,6 +2535,10 @@ def testReservedIpv6(self): self.assertFalse(ipaddress.ip_address('2001::').is_global) self.assertTrue(ipaddress.ip_address('2001:1::1').is_global) self.assertTrue(ipaddress.ip_address('2001:1::2').is_global) + # gh-151049: RFC 9665 anycast address (IANA 2024-04 registration) + self.assertTrue(ipaddress.ip_address('2001:1::3').is_global) + self.assertFalse(ipaddress.ip_address('2001:1::3').is_private) + self.assertFalse(ipaddress.ip_address('2001:1::4').is_global) self.assertFalse(ipaddress.ip_address('2001:2::').is_global) self.assertTrue(ipaddress.ip_address('2001:3::').is_global) self.assertFalse(ipaddress.ip_address('2001:4::').is_global) diff --git a/Misc/NEWS.d/next/Library/2026-06-07-23-30-34.gh-issue-151049.mBlToO.rst b/Misc/NEWS.d/next/Library/2026-06-07-23-30-34.gh-issue-151049.mBlToO.rst new file mode 100644 index 00000000000000..0fa338e62f56d1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-07-23-30-34.gh-issue-151049.mBlToO.rst @@ -0,0 +1,4 @@ +:mod:`ipaddress`: fix ``is_global`` and ``is_private`` for the +``2001:1::3/128`` (DNS-SD Service Registration Protocol Anycast) address, +which IANA registered as globally reachable per :rfc:`9665`. It is now +correctly reported with ``is_global`` ``True`` and ``is_private`` ``False``.