Skip to content

Commit 090f7e1

Browse files
authored
Merge branch 'main' into elif-error-message
2 parents dee5f1e + fda87c0 commit 090f7e1

35 files changed

+802
-307
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,7 @@ jobs:
658658
build_asan,
659659
build_tsan,
660660
test_hypothesis,
661+
cross-build-linux,
661662
'
662663
|| ''
663664
}}

.github/workflows/tail-call.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
- aarch64-apple-darwin/clang
4242
- x86_64-unknown-linux-gnu/gcc
4343
- aarch64-unknown-linux-gnu/gcc
44+
- free-threading
4445
llvm:
4546
- 19
4647
include:
@@ -65,6 +66,9 @@ jobs:
6566
- target: aarch64-unknown-linux-gnu/gcc
6667
architecture: aarch64
6768
runner: ubuntu-22.04-arm
69+
- target: free-threading
70+
architecture: x86_64
71+
runner: ubuntu-24.04
6872
steps:
6973
- uses: actions/checkout@v4
7074
with:
@@ -105,11 +109,20 @@ jobs:
105109
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
106110
107111
- name: Native Linux (release)
108-
if: runner.os == 'Linux'
112+
if: runner.os == 'Linux' && matrix.target != 'free-threading'
109113
run: |
110114
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
111115
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
112116
CC=clang-19 ./configure --with-tail-call-interp
113117
make all --jobs 4
114118
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
115119
120+
- name: Native Linux with free-threading (release)
121+
if: matrix.target == 'free-threading'
122+
run: |
123+
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
124+
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
125+
CC=clang-19 ./configure --with-tail-call-interp --disable-gil
126+
make all --jobs 4
127+
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
128+

Grammar/python.gram

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ import_name[stmt_ty]: 'import' a=dotted_as_names { _PyAST_Import(a, EXTRA) }
207207
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
208208
import_from[stmt_ty]:
209209
| 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
210-
_PyAST_ImportFrom(b->v.Name.id, c, _PyPegen_seq_count_dots(a), EXTRA) }
210+
_PyPegen_checked_future_import(p, b->v.Name.id, c, _PyPegen_seq_count_dots(a), EXTRA) }
211211
| 'from' a=('.' | '...')+ 'import' b=import_from_targets {
212212
_PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), EXTRA) }
213213
import_from_targets[asdl_alias_seq*]:

Include/internal/pycore_object.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
7474
{ \
7575
.ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL, \
7676
.ob_flags = _Py_STATICALLY_ALLOCATED_FLAG, \
77+
.ob_gc_bits = _PyGC_BITS_DEFERRED, \
7778
.ob_type = (type) \
7879
}
7980
#else
@@ -612,7 +613,7 @@ _Py_TryIncrefCompare(PyObject **src, PyObject *op)
612613
static inline int
613614
_Py_TryIncrefCompareStackRef(PyObject **src, PyObject *op, _PyStackRef *out)
614615
{
615-
if (_Py_IsImmortal(op) || _PyObject_HasDeferredRefcount(op)) {
616+
if (_PyObject_HasDeferredRefcount(op)) {
616617
*out = (_PyStackRef){ .bits = (intptr_t)op | Py_TAG_DEFERRED };
617618
return 1;
618619
}

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ extern PyStatus _Py_PreInitializeFromConfig(
7575

7676
extern wchar_t * _Py_GetStdlibDir(void);
7777

78-
extern int _Py_HandleSystemExit(int *exitcode_p);
78+
extern int _Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p);
7979

8080
extern PyObject* _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable);
8181

Include/internal/pycore_stackref.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ PyStackRef_FromPyObjectNew(PyObject *obj)
219219
// Make sure we don't take an already tagged value.
220220
assert(((uintptr_t)obj & Py_TAG_BITS) == 0);
221221
assert(obj != NULL);
222-
if (_Py_IsImmortal(obj) || _PyObject_HasDeferredRefcount(obj)) {
222+
if (_PyObject_HasDeferredRefcount(obj)) {
223223
return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED };
224224
}
225225
else {

Lib/sysconfig/__init__.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -666,34 +666,34 @@ def get_platform():
666666

667667
# Set for cross builds explicitly
668668
if "_PYTHON_HOST_PLATFORM" in os.environ:
669-
return os.environ["_PYTHON_HOST_PLATFORM"]
670-
671-
# Try to distinguish various flavours of Unix
672-
osname, host, release, version, machine = os.uname()
673-
674-
# Convert the OS name to lowercase, remove '/' characters, and translate
675-
# spaces (for "Power Macintosh")
676-
osname = osname.lower().replace('/', '')
677-
machine = machine.replace(' ', '_')
678-
machine = machine.replace('/', '-')
679-
680-
if osname[:5] == "linux":
681-
if sys.platform == "android":
682-
osname = "android"
683-
release = get_config_var("ANDROID_API_LEVEL")
684-
685-
# Wheel tags use the ABI names from Android's own tools.
686-
machine = {
687-
"x86_64": "x86_64",
688-
"i686": "x86",
689-
"aarch64": "arm64_v8a",
690-
"armv7l": "armeabi_v7a",
691-
}[machine]
692-
else:
693-
# At least on Linux/Intel, 'machine' is the processor --
694-
# i386, etc.
695-
# XXX what about Alpha, SPARC, etc?
696-
return f"{osname}-{machine}"
669+
osname, _, machine = os.environ["_PYTHON_HOST_PLATFORM"].partition('-')
670+
release = None
671+
else:
672+
# Try to distinguish various flavours of Unix
673+
osname, host, release, version, machine = os.uname()
674+
675+
# Convert the OS name to lowercase, remove '/' characters, and translate
676+
# spaces (for "Power Macintosh")
677+
osname = osname.lower().replace('/', '')
678+
machine = machine.replace(' ', '_')
679+
machine = machine.replace('/', '-')
680+
681+
if osname == "android" or sys.platform == "android":
682+
osname = "android"
683+
release = get_config_var("ANDROID_API_LEVEL")
684+
685+
# Wheel tags use the ABI names from Android's own tools.
686+
machine = {
687+
"x86_64": "x86_64",
688+
"i686": "x86",
689+
"aarch64": "arm64_v8a",
690+
"armv7l": "armeabi_v7a",
691+
}[machine]
692+
elif osname == "linux":
693+
# At least on Linux/Intel, 'machine' is the processor --
694+
# i386, etc.
695+
# XXX what about Alpha, SPARC, etc?
696+
return f"{osname}-{machine}"
697697
elif osname[:5] == "sunos":
698698
if release[0] >= "5": # SunOS 5 == Solaris 2
699699
osname = "solaris"
@@ -725,7 +725,7 @@ def get_platform():
725725
get_config_vars(),
726726
osname, release, machine)
727727

728-
return f"{osname}-{release}-{machine}"
728+
return '-'.join(map(str, filter(None, (osname, release, machine))))
729729

730730

731731
def get_python_version():

Lib/test/test_ast/test_ast.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,46 +3239,6 @@ def test_folding_tuple(self):
32393239

32403240
self.assert_ast(code, non_optimized_target, optimized_target)
32413241

3242-
def test_folding_comparator(self):
3243-
code = "1 %s %s1%s"
3244-
operators = [("in", ast.In()), ("not in", ast.NotIn())]
3245-
braces = [
3246-
("[", "]", ast.List, (1,)),
3247-
("{", "}", ast.Set, frozenset({1})),
3248-
]
3249-
for left, right, non_optimized_comparator, optimized_comparator in braces:
3250-
for op, node in operators:
3251-
non_optimized_target = self.wrap_expr(ast.Compare(
3252-
left=ast.Constant(1), ops=[node],
3253-
comparators=[non_optimized_comparator(elts=[ast.Constant(1)])]
3254-
))
3255-
optimized_target = self.wrap_expr(ast.Compare(
3256-
left=ast.Constant(1), ops=[node],
3257-
comparators=[ast.Constant(value=optimized_comparator)]
3258-
))
3259-
self.assert_ast(code % (op, left, right), non_optimized_target, optimized_target)
3260-
3261-
def test_folding_iter(self):
3262-
code = "for _ in %s1%s: pass"
3263-
braces = [
3264-
("[", "]", ast.List, (1,)),
3265-
("{", "}", ast.Set, frozenset({1})),
3266-
]
3267-
3268-
for left, right, ast_cls, optimized_iter in braces:
3269-
non_optimized_target = self.wrap_statement(ast.For(
3270-
target=ast.Name(id="_", ctx=ast.Store()),
3271-
iter=ast_cls(elts=[ast.Constant(1)]),
3272-
body=[ast.Pass()]
3273-
))
3274-
optimized_target = self.wrap_statement(ast.For(
3275-
target=ast.Name(id="_", ctx=ast.Store()),
3276-
iter=ast.Constant(value=optimized_iter),
3277-
body=[ast.Pass()]
3278-
))
3279-
3280-
self.assert_ast(code % (left, right), non_optimized_target, optimized_target)
3281-
32823242
def test_folding_type_param_in_function_def(self):
32833243
code = "def foo[%s = 1 + 1](): pass"
32843244

Lib/test/test_build_details.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import json
2+
import os
3+
import sys
4+
import sysconfig
5+
import string
6+
import unittest
7+
8+
from test.support import is_android, is_apple_mobile, is_emscripten, is_wasi
9+
10+
11+
class FormatTestsBase:
12+
@property
13+
def contents(self):
14+
"""Install details file contents. Should be overriden by subclasses."""
15+
raise NotImplementedError
16+
17+
@property
18+
def data(self):
19+
"""Parsed install details file data, as a Python object."""
20+
return json.loads(self.contents)
21+
22+
def key(self, name):
23+
"""Helper to fetch subsection entries.
24+
25+
It takes the entry name, allowing the usage of a dot to separate the
26+
different subsection names (eg. specifying 'a.b.c' as the key will
27+
return the value of self.data['a']['b']['c']).
28+
"""
29+
value = self.data
30+
for part in name.split('.'):
31+
value = value[part]
32+
return value
33+
34+
def test_parse(self):
35+
self.data
36+
37+
def test_top_level_container(self):
38+
self.assertIsInstance(self.data, dict)
39+
for key, value in self.data.items():
40+
with self.subTest(key=key):
41+
if key in ('schema_version', 'base_prefix', 'base_interpreter', 'platform'):
42+
self.assertIsInstance(value, str)
43+
elif key in ('language', 'implementation', 'abi', 'suffixes', 'libpython', 'c_api', 'arbitrary_data'):
44+
self.assertIsInstance(value, dict)
45+
46+
def test_base_prefix(self):
47+
self.assertIsInstance(self.key('base_prefix'), str)
48+
49+
def test_base_interpreter(self):
50+
"""Test the base_interpreter entry.
51+
52+
The generic test wants the key to be missing. If your implementation
53+
provides a value for it, you should override this test.
54+
"""
55+
with self.assertRaises(KeyError):
56+
self.key('base_interpreter')
57+
58+
def test_platform(self):
59+
self.assertEqual(self.key('platform'), sysconfig.get_platform())
60+
61+
def test_language_version(self):
62+
allowed_characters = string.digits + string.ascii_letters + '.'
63+
value = self.key('language.version')
64+
65+
self.assertLessEqual(set(value), set(allowed_characters))
66+
self.assertTrue(sys.version.startswith(value))
67+
68+
def test_language_version_info(self):
69+
value = self.key('language.version_info')
70+
71+
self.assertEqual(len(value), sys.version_info.n_fields)
72+
for part_name, part_value in value.items():
73+
with self.subTest(part=part_name):
74+
self.assertEqual(part_value, getattr(sys.version_info, part_name))
75+
76+
def test_implementation(self):
77+
for key, value in self.key('implementation').items():
78+
with self.subTest(part=key):
79+
if key == 'version':
80+
self.assertEqual(len(value), len(sys.implementation.version))
81+
for part_name, part_value in value.items():
82+
self.assertEqual(getattr(sys.implementation.version, part_name), part_value)
83+
else:
84+
self.assertEqual(getattr(sys.implementation, key), value)
85+
86+
87+
needs_installed_python = unittest.skipIf(
88+
sysconfig.is_python_build(),
89+
'This test can only run in an installed Python',
90+
)
91+
92+
93+
@unittest.skipIf(os.name != 'posix', 'Feature only implemented on POSIX right now')
94+
@unittest.skipIf(is_wasi or is_emscripten, 'Feature not available on WebAssembly builds')
95+
class CPythonBuildDetailsTests(unittest.TestCase, FormatTestsBase):
96+
"""Test CPython's install details file implementation."""
97+
98+
@property
99+
def location(self):
100+
if sysconfig.is_python_build():
101+
projectdir = sysconfig.get_config_var('projectbase')
102+
with open(os.path.join(projectdir, 'pybuilddir.txt')) as f:
103+
dirname = os.path.join(projectdir, f.read())
104+
else:
105+
dirname = sysconfig.get_path('stdlib')
106+
return os.path.join(dirname, 'build-details.json')
107+
108+
@property
109+
def contents(self):
110+
with open(self.location, 'r') as f:
111+
return f.read()
112+
113+
@needs_installed_python
114+
def test_location(self):
115+
self.assertTrue(os.path.isfile(self.location))
116+
117+
# Override generic format tests with tests for our specific implemenation.
118+
119+
@needs_installed_python
120+
@unittest.skipIf(is_android or is_apple_mobile, 'Android and iOS run tests via a custom testbed method that changes sys.executable')
121+
def test_base_interpreter(self):
122+
value = self.key('base_interpreter')
123+
124+
self.assertEqual(os.path.realpath(value), os.path.realpath(sys.executable))
125+
126+
127+
if __name__ == '__main__':
128+
unittest.main()

Lib/test/test_compile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ def check_same_constant(const):
798798
f3 = lambda x: x in {("not a name",)}
799799
self.assertIs(f1.__code__.co_consts[0],
800800
f2.__code__.co_consts[0][0])
801-
self.assertIs(next(iter(f3.__code__.co_consts[0])),
801+
self.assertIs(next(iter(f3.__code__.co_consts[1])),
802802
f2.__code__.co_consts[0])
803803

804804
# {0} is converted to a constant frozenset({0}) by the peephole

0 commit comments

Comments
 (0)