Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add more statement starters to pyparse._synchre
"with", "del", "global", "nonlocal", "pass" and "finally" cannot start
a continuation line, so they are safe synchronization points, and let
find_good_parse_start() cut the parsed text closer to its end.
  • Loading branch information
serhiy-storchaka committed Sep 17, 2026
commit 041a12f9d5786cf375a53e05329db7fdf48e7d7a
27 changes: 17 additions & 10 deletions Lib/idlelib/idle_test/test_pyparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -91,18 +91,18 @@ 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.
eq(start(is_char_in_string=lambda index: index > pos), pos)
# 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.
Expand All @@ -111,12 +111,13 @@ 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.
Expand All @@ -127,18 +128,24 @@ def char_in_string_false(index): return False
setcode('if x:\n'
' pass\n'
'else:\n'
' pass\n')
' x = 1\n')
eq(start(char_in_string_false), 15)
setcode('if x:\n'
' pass\n'
'else : # comment\n'
' pass\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 = (
Expand Down
6 changes: 6 additions & 0 deletions Lib/idlelib/pyparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
| except
| raise
| import
| with
| del
| global
| nonlocal
| pass
| finally
)
\b
""", re.VERBOSE | re.MULTILINE).search
Expand Down
Loading