Skip to content

Commit edd652a

Browse files
generatedunixname1734921407115435meta-codesync[bot]
authored andcommitted
Sync pre-release CPython 3.15 branch from GitHub (2026-09-19)
Summary: Imported python/cpython `3.15.0rc2+dev` from upstream rev [`b5cbbb2`](https://www.github.com/python/cpython/commit/b5cbbb288f2e0d48e1b0c36a82825e14965c983e) (committed 2026-09-19 22:37:33+00:00). # Commit Info - Base: (`3.15.0rc2+dev`) - [`8b3ba4c`](https://www.github.com/python/cpython/commit/8b3ba4c78911f95a01cd32562b2d4e525da09989) (commit date: 2026-09-19 04:49:07+00:00) - Imported: (`3.15.0rc2+dev`) - [`b5cbbb2`](https://www.github.com/python/cpython/commit/b5cbbb288f2e0d48e1b0c36a82825e14965c983e) (commit date: 2026-09-19 22:37:33+00:00) # Noteworthy file changes - Low-signal files (2 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GMYqrjBbD-OHBzFmADuaIV9TZMx3br0LAAAz Differential Revision: D120880508 fbshipit-source-id: 2de739444f196c71cea5725d925fa97cde3e0c5c
1 parent d67e904 commit edd652a

16 files changed

Lines changed: 330 additions & 205 deletions

‎Doc/Makefile‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ lock:
191191
# Dependencies have a 14 day cooldown period to mitigate supply chain attacks,
192192
# except for sphinx_linklint and python-docs-theme, which are maintained by
193193
# core team members.
194-
uv pip compile requirements.txt \
195-
--exclude-newer P14D \
194+
$(UV) pip compile requirements.txt \
195+
--upgrade --exclude-newer P14D \
196196
--exclude-newer-package sphinx_linklint=PT0S \
197197
--exclude-newer-package python-docs-theme=PT0S \
198198
--no-cache --output-file $(REQUIREMENTS) \

‎Doc/conf.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,3 +638,40 @@
638638
"library/threadsafety.rst": "builtins/threadsafety.rst",
639639
"library/time-complexity.rst": "builtins/time-complexity.rst",
640640
}
641+
642+
# Refuse to run the doctest builder under a mismatched Python
643+
# -----------------------------------------------------------
644+
645+
646+
def _check_doctest_interpreter(app):
647+
# The doctests are executed by the interpreter running Sphinx,
648+
# so refuse to run them if its version doesn't match the source tree.
649+
if app.builder.name != "doctest":
650+
return
651+
652+
running_version = f"{sys.version_info.major}.{sys.version_info.minor}"
653+
if running_version != version:
654+
from sphinx.util import logging as sphinx_logging
655+
656+
logger = sphinx_logging.getLogger(__name__)
657+
logger.error(
658+
"The doctests are executed by the Python running Sphinx, "
659+
"which is Python %s, however this source tree is Python %s, "
660+
"so they would test Python %s rather than the code "
661+
"documented here.\n"
662+
"Recreate the venv with a matching interpreter, for example: "
663+
"'make clean-venv && make venv PYTHON=../python'.",
664+
running_version,
665+
version,
666+
running_version,
667+
)
668+
raise SystemExit(1)
669+
670+
671+
def setup(app):
672+
app.connect("builder-inited", _check_doctest_interpreter)
673+
return {
674+
"version": "1.0",
675+
"parallel_read_safe": True,
676+
"parallel_write_safe": True,
677+
}

‎Doc/library/turtle.rst‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2571,6 +2571,25 @@ Public classes
25712571
* ``a.rotate(angle)`` rotation
25722572

25732573

2574+
Exceptions
2575+
==========
2576+
2577+
The :mod:`!turtle` module defines the following exception:
2578+
2579+
.. exception:: TurtleGraphicsError
2580+
2581+
Raised for invalid arguments or operations.
2582+
For example, a malformed color string:
2583+
2584+
.. doctest::
2585+
:skipif: _tkinter is None
2586+
2587+
>>> turtle.color("blau")
2588+
Traceback (most recent call last):
2589+
...
2590+
turtle.TurtleGraphicsError: bad color string: blau
2591+
2592+
25742593
.. _turtle-explanation:
25752594

25762595
Explanation

‎Doc/pylock.toml‎

Lines changed: 140 additions & 82 deletions
Large diffs are not rendered by default.

‎Lib/test/test_fstring.py‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
import re
1515
import types
1616
import decimal
17+
import subprocess
1718
import unittest
1819
import warnings
1920
from test import support
2021
from test.support.os_helper import temp_cwd
21-
from test.support.script_helper import assert_python_failure, assert_python_ok
22+
from test.support.script_helper import (
23+
assert_python_failure, assert_python_ok, spawn_python)
2224

2325
a_global = 'global variable'
2426

@@ -822,6 +824,18 @@ def build_fstr(n, extra=''):
822824
s = "f'{1}' 'x' 'y'" * 1024
823825
self.assertEqual(eval(s), '1xy' * 1024)
824826

827+
@support.requires_resource('cpu')
828+
def test_many_fstrings_in_module(self):
829+
fields = ''.join(f'{{x{i}}}' for i in range(100))
830+
source = ''.join(
831+
f"value_{i} = f'{fields}'\n" for i in range(1_000)
832+
)
833+
namespace = {f'x{i}': str(i) for i in range(100)}
834+
expected = ''.join(str(i) for i in range(100))
835+
exec(source, namespace)
836+
self.assertEqual(namespace['value_0'], expected)
837+
self.assertEqual(namespace['value_999'], expected)
838+
825839
def test_format_specifier_expressions(self):
826840
width = 10
827841
precision = 4
@@ -1338,6 +1352,9 @@ def test_not_equal(self):
13381352
self.assertEqual(f'{3!=4:}', 'True')
13391353
self.assertEqual(f'{3!=4!s}', 'True')
13401354
self.assertEqual(f'{3!=4!s:.3}', 'Tru')
1355+
a = 3
1356+
b = 4
1357+
self.assertEqual(f'{a!=b=:>10}', 'a!=b= 1')
13411358

13421359
def test_equal_equal(self):
13431360
# Because an expression ending in = has special meaning,
@@ -1792,6 +1809,32 @@ def test_debug_in_file(self):
17921809
self.assertEqual(stdout.decode('utf-8').strip().replace('\r\n', '\n').replace('\r', '\n'),
17931810
"3\n=3")
17941811

1812+
@support.requires_subprocess()
1813+
def test_expression_in_interactive_after_buffer_resize(self):
1814+
expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)"
1815+
source = (
1816+
f"result = f'''{{{expression}=}}'''\n"
1817+
"print(repr(result))\n"
1818+
)
1819+
with spawn_python('-i', '-q', stderr=subprocess.PIPE) as process:
1820+
stdout, stderr = process.communicate(
1821+
source.encode(), timeout=support.SHORT_TIMEOUT)
1822+
self.assertEqual(process.returncode, 0, stderr)
1823+
self.assertEqual(stdout.decode().strip(), repr(expression + "=1"))
1824+
1825+
def test_debug_in_file_after_buffer_resize(self):
1826+
expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)"
1827+
expected = expression + "=1"
1828+
with temp_cwd():
1829+
script = 'script.py'
1830+
source = (
1831+
f"result = f'''{{{expression}=}}'''\n"
1832+
f"assert result == {expected!r}\n"
1833+
)
1834+
with open(script, 'w') as f:
1835+
f.write(source)
1836+
assert_python_ok(script)
1837+
17951838
def test_syntax_warning_infinite_recursion_in_file(self):
17961839
with temp_cwd():
17971840
script = 'script.py'

‎Lib/test/test_tstring.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import subprocess
12
import unittest
23

4+
from test import support
5+
from test.support.os_helper import temp_cwd
6+
from test.support.script_helper import assert_python_ok, spawn_python
37
from test.test_string._support import TStringBaseCase, fstring
48

59

@@ -79,6 +83,44 @@ def upper(self):
7983
)
8084
self.assertEqual(fstring(t), "Name: Bob, Age: 30")
8185

86+
@support.requires_subprocess()
87+
def test_expression_in_interactive_after_buffer_resize(self):
88+
expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)"
89+
source = (
90+
f"result = t'''{{{expression}}}'''\n"
91+
"print(repr(result.interpolations[0].expression))\n"
92+
)
93+
with spawn_python('-i', '-q', stderr=subprocess.PIPE) as process:
94+
stdout, stderr = process.communicate(
95+
source.encode(), timeout=support.SHORT_TIMEOUT)
96+
self.assertEqual(process.returncode, 0, stderr)
97+
self.assertEqual(stdout.decode().strip(), repr(expression))
98+
99+
def test_interpolation_expression_in_file_after_buffer_resize(self):
100+
expression = "(\n" + (" " * 64 + "\n") * 256 + "1\n)"
101+
with temp_cwd():
102+
script = 'script.py'
103+
source = (
104+
f"template = t'''{{{expression}}}'''\n"
105+
"interpolation = template.interpolations[0]\n"
106+
f"assert interpolation.expression == {expression!r}\n"
107+
)
108+
with open(script, 'w') as f:
109+
f.write(source)
110+
assert_python_ok(script)
111+
112+
@support.requires_resource('cpu')
113+
def test_many_tstrings_in_module(self):
114+
fields = ''.join(f'{{x{i}}}' for i in range(100))
115+
source = ''.join(
116+
f"value_{i} = t'{fields}'\n" for i in range(1_000)
117+
)
118+
namespace = {f'x{i}': str(i) for i in range(100)}
119+
expected = ''.join(str(i) for i in range(100))
120+
exec(source, namespace)
121+
self.assertEqual(fstring(namespace['value_0']), expected)
122+
self.assertEqual(fstring(namespace['value_999']), expected)
123+
82124
def test_format_specifiers(self):
83125
# Test basic format specifiers
84126
value = 3.14159
@@ -88,6 +130,14 @@ def test_format_specifiers(self):
88130
)
89131
self.assertEqual(fstring(t), "Pi: 3.14")
90132

133+
a = 3
134+
b = 4
135+
t = t"{a!=b:>10}"
136+
self.assertTStringEqual(
137+
t, ("", ""), [(a != b, "a!=b", None, ">10")]
138+
)
139+
self.assertEqual(fstring(t), " 1")
140+
91141
def test_conversions(self):
92142
# Test !s conversion (str)
93143
obj = object()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Use ``sysconf(_SC_PAGESIZE)`` instead of ``getpagesize()`` in
2+
``remote_debug.h`` so ``_remote_debugging`` builds on Darwin with
3+
``-Werror=implicit-function-declaration``.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix quadratic-time tokenization of modules containing many f-strings or
2+
t-strings.

‎Parser/lexer/buffer.c‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ _PyLexer_remember_fstring_buffers(struct tok_state *tok)
1515
mode = &(tok->tok_mode_stack[index]);
1616
mode->start_offset = mode->start == NULL ? -1 : mode->start - tok->buf;
1717
mode->multi_line_start_offset = mode->multi_line_start == NULL ? -1 : mode->multi_line_start - tok->buf;
18+
mode->last_expr_start_offset = mode->last_expr_start == NULL
19+
? -1 : mode->last_expr_start - tok->buf;
1820
}
1921
}
2022

@@ -29,6 +31,8 @@ _PyLexer_restore_fstring_buffers(struct tok_state *tok)
2931
mode = &(tok->tok_mode_stack[index]);
3032
mode->start = mode->start_offset < 0 ? NULL : tok->buf + mode->start_offset;
3133
mode->multi_line_start = mode->multi_line_start_offset < 0 ? NULL : tok->buf + mode->multi_line_start_offset;
34+
mode->last_expr_start = mode->last_expr_start_offset < 0
35+
? NULL : tok->buf + mode->last_expr_start_offset;
3236
}
3337
}
3438

0 commit comments

Comments
 (0)