Skip to content

Commit 48a8fe8

Browse files
Merge remote-tracking branch 'upstream/3.15' into mi-155672
2 parents 185b92e + 26fa570 commit 48a8fe8

67 files changed

Lines changed: 1101 additions & 233 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Doc/builtins/functions.rst‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,14 @@ are always available. They are listed here in alphabetical order.
344344
It is needed to unambiguous :ref:`filter <warning-filter>` syntax warnings
345345
by module name.
346346

347-
This function raises :exc:`SyntaxError` or :exc:`ValueError` if the compiled
348-
source is invalid.
347+
This function raises :exc:`SyntaxError` if the compiled source is invalid,
348+
including a *source* containing a null character or that cannot be decoded;
349+
:exc:`ValueError` if *mode* or *flags* is invalid,
350+
or if a string *source* contains surrogate characters;
351+
:exc:`MemoryError` or :exc:`RecursionError` if *source* is too complex
352+
to parse or compile,
353+
for example an expression with many thousands of nested operators;
354+
and :exc:`OverflowError` if *source* is too large.
349355

350356
If you want to parse Python code into its AST representation, see
351357
:func:`ast.parse`.
@@ -377,11 +383,15 @@ are always available. They are listed here in alphabetical order.
377383
Previously, :exc:`TypeError` was raised when null bytes were encountered
378384
in *source*.
379385

380-
.. versionadded:: 3.8
386+
.. versionchanged:: 3.8
381387
``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` can now be passed in flags to enable
382388
support for top-level ``await``, ``async for``, and ``async with``.
383389

384-
.. versionadded:: 3.15
390+
.. versionchanged:: 3.12
391+
:exc:`SyntaxError` is raised instead of :exc:`ValueError` when null bytes
392+
are encountered in *source*.
393+
394+
.. versionchanged:: 3.15
385395
Added the *module* parameter.
386396

387397

‎Doc/library/code.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Interactive Interpreter Objects
9292
*symbol* is ``'single'``. One of several things can happen:
9393

9494
* The input is incorrect; :func:`compile_command` raised an exception
95-
(:exc:`SyntaxError` or :exc:`OverflowError`). A syntax traceback will be
95+
(usually :exc:`SyntaxError`). A syntax traceback will be
9696
printed by calling the :meth:`showsyntaxerror` method. :meth:`runsource`
9797
returns ``False``.
9898

‎Lib/_pyrepl/console.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
261261
)
262262
self.showsyntaxerror(filename, source=source)
263263
return False
264-
except (OverflowError, ValueError):
264+
except Exception:
265265
self.showsyntaxerror(filename, source=source)
266266
return False
267267
if tree.body:
@@ -282,7 +282,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
282282
)
283283
self.showsyntaxerror(filename, source=source)
284284
return False
285-
except (OverflowError, ValueError):
285+
except Exception:
286286
self.showsyntaxerror(filename, source=source)
287287
return False
288288

‎Lib/_pyrepl/simple_interact.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def _more_lines(console: code.InteractiveConsole, unicodetext: str) -> bool:
8383
src = _strip_final_indent(unicodetext)
8484
try:
8585
code = console.compile(src, "<stdin>", "single")
86-
except (OverflowError, SyntaxError, ValueError):
86+
except Exception:
8787
lines = src.splitlines(keepends=True)
8888
if len(lines) == 1:
8989
return False

‎Lib/code.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def runsource(self, source, filename="<input>", symbol="single"):
4444
One of several things can happen:
4545
4646
1) The input is incorrect; compile_command() raised an
47-
exception (SyntaxError or OverflowError). A syntax traceback
48-
will be printed by calling the showsyntaxerror() method.
47+
exception (usually SyntaxError). A syntax traceback will be
48+
printed by calling the showsyntaxerror() method.
4949
5050
2) The input is incomplete, and more input is required;
5151
compile_command() returned None. Nothing happens.
@@ -62,7 +62,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
6262
"""
6363
try:
6464
code = self.compile(source, filename, symbol)
65-
except (OverflowError, SyntaxError, ValueError):
65+
except Exception:
6666
# Case 1
6767
self.showsyntaxerror(filename, source=source)
6868
return False

‎Lib/idlelib/__main__.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,12 @@
33
44
Run IDLE as python -m idlelib
55
"""
6+
import sys
7+
8+
if not sys.flags.safe_path:
9+
# Remove the current directory, prepended by "python -m", so that
10+
# user files do not shadow IDLE's imports (gh-70331).
11+
del sys.path[0]
12+
613
import idlelib.pyshell
714
idlelib.pyshell.main()

‎Lib/idlelib/colorizer.py‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88

99
DEBUG = False
1010

11+
# Adding a tag to a line takes time proportional to the number of tags
12+
# already in the line, so only the beginning of a line is colorized;
13+
# the rest is usually not visible anyway (gh-103089).
14+
MAX_COLORIZED_LINE = 2000
15+
1116

1217
def any(name, alternates):
1318
"Return a named group pattern matching list of alternates."
@@ -42,6 +47,11 @@ def make_pat():
4247
]) +
4348
r"))"
4449
)
50+
type_softkw = (
51+
r"^[ \t]*" + # at beginning of line + possible indentation
52+
r"(?P<TYPE_SOFTKW>type)" +
53+
r"(?=[ \t]+(?!(?:" + "|".join(keyword.kwlist) + r")\b)[^\W\d])"
54+
)
4555
lazy_softkw = ( # lazy new in 3.15 (+ 2 lines below).
4656
r"^[ \t]*" + # at beginning of line + possible indentation
4757
r"(?P<LAZY_SOFTKW>lazy)" +
@@ -59,7 +69,7 @@ def make_pat():
5969
dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
6070
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
6171
prog = re.compile("|".join([
62-
builtin, comment, string, kw,
72+
type_softkw, builtin, comment, string, kw,
6373
match_softkw, case_default,
6474
case_softkw_and_pattern, lazy_softkw,
6575
any("SYNC", [r"\n"]),
@@ -75,6 +85,7 @@ def make_pat():
7585
"CASE_SOFTKW": "KEYWORD",
7686
"CASE_DEFAULT_UNDERSCORE": "KEYWORD",
7787
"CASE_SOFTKW2": "KEYWORD",
88+
"TYPE_SOFTKW": "KEYWORD",
7889
"LAZY_SOFTKW": "KEYWORD",
7990
}
8091

@@ -344,16 +355,39 @@ def _add_tags_in_section(self, chars, head):
344355
`chars` is a string with the text to parse and to which
345356
highlighting is to be applied.
346357
347-
`head` is the index in the text widget where the text is found.
358+
`head` is the index in the text widget where the text is found.
348359
"""
349-
for m in self.prog.finditer(chars):
360+
# Positions are relative to the start of the current line, so that
361+
# Tk does not resolve them through the previous lines.
362+
line = int(head.split('.')[0])
363+
line_start = 0 # Offset of the current line in chars.
364+
tags = []
365+
pos = 0
366+
while True:
367+
m = self.prog.search(chars, pos)
368+
if m is None:
369+
break
350370
for name, matched_text in matched_named_groups(m):
351371
a, b = m.span(name)
352-
self._add_tag(a, b, head, name)
372+
tags.append((a - line_start, b - line_start, head, name))
353373
if matched_text in ("def", "class"):
354374
if m1 := self.idprog.match(chars, b):
355375
a, b = m1.span(1)
356-
self._add_tag(a, b, head, "DEFINITION")
376+
tags.append((a - line_start, b - line_start,
377+
head, "DEFINITION"))
378+
pos = m.end()
379+
if '\n' in m[0]:
380+
line += m[0].count('\n')
381+
line_start = m.start() + m[0].rindex('\n') + 1
382+
head = f"{line}.0"
383+
elif pos - line_start >= MAX_COLORIZED_LINE:
384+
# The rest of a long line is not colorized.
385+
pos = chars.find('\n', pos)
386+
if pos < 0:
387+
break
388+
# Adding a tag is faster if there are no tags after it.
389+
for args in reversed(tags):
390+
self._add_tag(*args)
357391

358392
def removecolors(self):
359393
"Remove all colorizing tags."

‎Lib/idlelib/editor.py‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,19 +1120,50 @@ def _close(self):
11201120

11211121
def last_mtime(self):
11221122
file = self.io.filename
1123-
return os.path.getmtime(file) if file else 0
1123+
if not file:
1124+
return None
1125+
try:
1126+
return os.path.getmtime(file)
1127+
except OSError:
1128+
# File is gone or cannot be stat'ed.
1129+
return None
11241130

11251131
def focus_in_event(self, event):
11261132
mtime = self.last_mtime()
1127-
if self.mtime != mtime:
1133+
if mtime == self.mtime:
1134+
return
1135+
if self.mtime is not None and mtime is None:
1136+
# The file was there and is now gone; reloading cannot work.
1137+
self.deleted_file_event(event)
1138+
else:
11281139
self.mtime = mtime
1129-
if self. askyesno(
1140+
if self.askyesno(
11301141
'Reload', '"%s"\n\nThis script has been modified by another program.'
11311142
'\nDo you want to reload it?' % self.io.filename, parent=self.text):
11321143
self.io.loadfile(self.io.filename)
11331144
else:
11341145
self.set_saved(False)
11351146

1147+
def deleted_file_event(self, event):
1148+
# The file was deleted or renamed while open; ask what to do with the
1149+
# buffer instead of offering a reload that could only fail. Forget the
1150+
# old mtime before showing the dialog so a FocusIn delivered while this
1151+
# dialog (or a Close/Save As sub-dialog) is open does not reopen it; a
1152+
# successful Save As restores it via set_saved(True).
1153+
self.mtime = None
1154+
dialog = simpledialog.SimpleDialog(
1155+
self.text,
1156+
title='File Deleted',
1157+
text='"%s"\n\nThis file no longer exists.' % self.io.filename,
1158+
buttons=('Close', 'Save As', 'Ignore'),
1159+
default=1,
1160+
cancel=2)
1161+
choice = dialog.go()
1162+
if choice == 0:
1163+
self.close()
1164+
elif choice == 1:
1165+
self.io.save_as(event)
1166+
11361167
def load_extensions(self):
11371168
self.extensions = {}
11381169
self.load_standard_extensions()

‎Lib/idlelib/idle.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
import os.path
21
import sys
32

3+
if __spec__ is not None and not sys.flags.safe_path:
4+
# Remove the current directory, prepended by "python -m", so that
5+
# user files do not shadow IDLE's imports (gh-70331).
6+
del sys.path[0]
7+
8+
import os.path
9+
410

511
# Enable running IDLE with idlelib in a non-standard location.
612
# This was once used to run development versions of IDLE.

‎Lib/idlelib/idle_test/test_colorizer.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ async def f(): await g()
5252
'''
5353
case _:'''
5454
"match x:"
55+
type Point = tuple[float, float]
56+
type = type(1)
5557
""")
5658

5759

@@ -404,6 +406,8 @@ def test_recolorize_main(self, mock_notify):
404406
('28.25', ('STRING',)), ('28.38', ('STRING',)),
405407
('30.0', ('STRING',)),
406408
('31.1', ('STRING',)),
409+
('32.0', ('KEYWORD',)),
410+
('33.0', ('BUILTIN',)), ('33.1', ('BUILTIN',)),
407411
# SYNC at the end of every line.
408412
('1.55', ('SYNC',)), ('2.50', ('SYNC',)), ('3.34', ('SYNC',)),
409413
)
@@ -434,7 +438,7 @@ def test_recolorize_main(self, mock_notify):
434438
eq(text.tag_nextrange('STRING', '8.12'), ('8.14', '8.17'))
435439
eq(text.tag_nextrange('STRING', '8.17'), ('8.19', '8.26'))
436440
eq(text.tag_nextrange('SYNC', '8.0'), ('8.26', '9.0'))
437-
eq(text.tag_nextrange('SYNC', '31.0'), ('31.10', '33.0'))
441+
eq(text.tag_nextrange('SYNC', '31.0'), ('31.10', '32.0'))
438442

439443
def _assert_highlighting(self, source, tag_ranges):
440444
"""Check highlighting of a given piece of code.
@@ -569,6 +573,18 @@ def test_long_multiline_string(self):
569573
e"""
570574
''')
571575
self._assert_highlighting(source, {'STRING': [('1.0', '5.4')]})
576+
source = '"""a\nb""" + str\n'
577+
self._assert_highlighting(source, {'STRING': [('1.0', '2.4')],
578+
'BUILTIN': [('2.7', '2.10')]})
579+
580+
def test_long_line(self):
581+
# gh-103089: only the first MAX_COLORIZED_LINE characters of a line
582+
# are colorized.
583+
n = colorizer.MAX_COLORIZED_LINE
584+
source = f"pass\n{'x' * (n - 3)}'a', 'b'\n'c'\n"
585+
self._assert_highlighting(source, {'KEYWORD': [('1.0', '1.4')],
586+
'STRING': [(f'2.{n-3}', f'2.{n}'),
587+
('3.0', '3.3')]})
572588

573589
@run_in_tk_mainloop(delay=50)
574590
def test_incremental_editing(self):

0 commit comments

Comments
 (0)