https://github.com/python/cpython/commit/8cceb109378b9e80cf5db0577f37589ad7d2c9ee
commit: 8cceb109378b9e80cf5db0577f37589ad7d2c9ee
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-09-22T19:20:00Z
summary:

[3.13] gh-85560: Fix IDLE paren matching with "else" or "yield" at the start of 
a continuation line (GH-157693) (#157961)

gh-85560: Fix IDLE paren matching with "else" or "yield" at the start of a 
continuation line (GH-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.
(cherry picked from commit 20734734802d7505823c8f2eeafd003d5b7fe4f1)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/IDLE/2026-09-17-18-00-00.gh-issue-85560.elseexpr.rst
M Lib/idlelib/idle_test/test_pyparse.py
M Lib/idlelib/pyparse.py

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``.

_______________________________________________
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]

Reply via email to