Skip to content

Commit 6a77bee

Browse files
committed
Merge remote-tracking branch 'origin/main' into gh-158195-unpin-type-lock
2 parents 4653f4d + 5503a5e commit 6a77bee

34 files changed

Lines changed: 1065 additions & 212 deletions

‎Doc/library/ast.rst‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ Node classes
4848
module and re-exported in :mod:`!ast`.
4949

5050
There is one class defined for each left-hand side symbol in the abstract
51-
grammar (for example, :class:`ast.stmt` or :class:`ast.expr`). In addition,
51+
grammar (for example, :class:`!ast.stmt` or :class:`!ast.expr`). In addition,
5252
there is one class defined for each constructor on the right-hand side; these
5353
classes inherit from the classes for the left-hand side trees. For example,
54-
:class:`ast.BinOp` inherits from :class:`ast.expr`. For production rules
54+
:class:`ast.BinOp` inherits from :class:`!ast.expr`. For production rules
5555
with alternatives (aka "sums"), the left-hand side class is abstract: only
5656
instances of specific constructor nodes are ever created.
5757

@@ -65,7 +65,7 @@ Node classes
6565

6666
Each instance of a concrete class has one attribute for each child node,
6767
of the type as defined in the grammar. For example, :class:`ast.BinOp`
68-
instances have an attribute :attr:`left` of type :class:`ast.expr`.
68+
instances have an attribute :attr:`!left` of type :class:`!ast.expr`.
6969

7070
If these attributes are marked as optional in the grammar (using a
7171
question mark), the value might be ``None``. If the attributes can have
@@ -104,10 +104,11 @@ Node classes
104104
one can get the source segment of a one-line expression node using
105105
``source_line[node.col_offset : node.end_col_offset]``.
106106

107-
The constructor of a class :class:`ast.T` parses its arguments as follows:
107+
The constructors of AST node classes parse their arguments as follows:
108108

109109
* If there are positional arguments, there must be as many as there are items
110-
in :attr:`T._fields`; they will be assigned as attributes of these names.
110+
in :attr:`!_fields`; they will be assigned to attributes with
111+
the corresponding names.
111112
* If there are keyword arguments, they will set the attributes of the same
112113
names to the given values.
113114

@@ -2223,8 +2224,8 @@ Async and await
22232224

22242225
.. note::
22252226
When a string is parsed by :func:`ast.parse`, operator nodes (subclasses
2226-
of :class:`ast.operator`, :class:`ast.unaryop`, :class:`ast.cmpop`,
2227-
:class:`ast.boolop` and :class:`ast.expr_context`) on the returned tree
2227+
of :class:`!ast.operator`, :class:`!ast.unaryop`, :class:`!ast.cmpop`,
2228+
:class:`!ast.boolop` and :class:`!ast.expr_context`) on the returned tree
22282229
will be singletons. Changes to one will be reflected in all other
22292230
occurrences of the same value (for example, :class:`ast.Add`).
22302231

‎Lib/collections/__init__.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,8 @@ def encode(self, encoding='utf-8', errors='strict'):
15361536
return self.data.encode(encoding, errors)
15371537

15381538
def endswith(self, suffix, start=0, end=_sys.maxsize):
1539+
if isinstance(suffix, UserString):
1540+
suffix = suffix.data
15391541
return self.data.endswith(suffix, start, end)
15401542

15411543
def expandtabs(self, tabsize=8):
@@ -1646,6 +1648,8 @@ def splitlines(self, keepends=False):
16461648
return self.data.splitlines(keepends)
16471649

16481650
def startswith(self, prefix, start=0, end=_sys.maxsize):
1651+
if isinstance(prefix, UserString):
1652+
prefix = prefix.data
16491653
return self.data.startswith(prefix, start, end)
16501654

16511655
def strip(self, chars=None):

‎Lib/test/test_clinic.py‎

Lines changed: 182 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
import re
1616
import sys
1717
import unittest
18+
from unittest import mock
1819
import warnings
1920

2021
test_tools.skip_if_missing('clinic')
2122
with test_tools.imports_under_tool('clinic'):
2223
import libclinic
2324
from libclinic import ClinicError, unspecified, NULL, fail
2425
from libclinic.converters import (
25-
int_converter, object_converter, str_converter, self_converter)
26+
int_converter, object_converter, str_converter, self_converter,
27+
Py_ssize_t_converter)
2628
from libclinic.function import (
2729
Module, Class, Function, FunctionKind, Parameter,
2830
permute_optional_groups, permute_right_option_groups,
@@ -34,6 +36,7 @@
3436
from libclinic.block_parser import Block, BlockParser
3537
from libclinic.codegen import BlockPrinter, Destination
3638
from libclinic.dsl_parser import DSLParser
39+
from libclinic.slots import SlotSpec, SLOTS
3740
from libclinic.cli import parse_file, Clinic
3841

3942

@@ -807,6 +810,112 @@ def test_var_keyword_non_dict(self):
807810
"""
808811
self.expect_failure(block, err, lineno=4)
809812

813+
def test_slot_next(self):
814+
block = """
815+
/*[clinic input]
816+
output everything block
817+
class Foo "FooObject *" "&Foo_Type"
818+
[clinic start generated code]*/
819+
/*[clinic input]
820+
Foo.__next__
821+
[clinic start generated code]*/
822+
/*[clinic input]
823+
@critical_section
824+
Foo.__next__ as foo_iternext
825+
[clinic start generated code]*/
826+
#ifdef CONDITION
827+
/*[clinic input]
828+
Foo.__next__ as foo_iternext_cond
829+
[clinic start generated code]*/
830+
#endif
831+
"""
832+
generated = self.clinic.parse(dedent(block))
833+
self.assertIn(dedent("""
834+
static PyObject *
835+
Foo_next(PyObject *self)
836+
{
837+
return Foo_next_impl((FooObject *)self);
838+
}
839+
"""), generated)
840+
self.assertIn(dedent("""
841+
static PyObject *
842+
foo_iternext(PyObject *self)
843+
{
844+
PyObject *return_value = NULL;
845+
846+
Py_BEGIN_CRITICAL_SECTION(self);
847+
return_value = foo_iternext_impl((FooObject *)self);
848+
Py_END_CRITICAL_SECTION();
849+
850+
return return_value;
851+
}
852+
"""), generated)
853+
self.assertIn("#if defined(CONDITION)\n\nstatic PyObject *\n"
854+
"foo_iternext_cond_impl(FooObject *self);", generated)
855+
# A slot function is not stored in a PyMethodDef.
856+
for text in "METHODDEF", "PyDoc_", "#ifndef":
857+
self.assertNotIn(text, generated)
858+
859+
def test_slot_with_parameters_and_return_type(self):
860+
# Slots which take arguments or return something else than an
861+
# object are not supported yet; check the machinery with fake ones.
862+
slots = {
863+
'__contains__': SlotSpec('__contains__', 'int', 'int',
864+
{(object_converter,): 'sq_contains'}),
865+
'__bool__': SlotSpec('__bool__', 'int', 'int', {(): 'nb_bool'}),
866+
}
867+
block = """
868+
/*[clinic input]
869+
output everything block
870+
class Foo "FooObject *" "&Foo_Type"
871+
[clinic start generated code]*/
872+
/*[clinic input]
873+
Foo.__contains__
874+
value: object
875+
/
876+
[clinic start generated code]*/
877+
/*[clinic input]
878+
@critical_section
879+
Foo.__bool__
880+
[clinic start generated code]*/
881+
"""
882+
with mock.patch.dict(SLOTS, slots):
883+
generated = self.clinic.parse(dedent(block))
884+
self.assertIn(dedent("""
885+
static int
886+
Foo_contains(PyObject *self, PyObject *value)
887+
{
888+
return Foo_contains_impl((FooObject *)self, value);
889+
}
890+
"""), generated)
891+
self.assertIn(dedent("""
892+
static int
893+
Foo_bool(PyObject *self)
894+
{
895+
int return_value = -1;
896+
897+
Py_BEGIN_CRITICAL_SECTION(self);
898+
return_value = Foo_bool_impl((FooObject *)self);
899+
Py_END_CRITICAL_SECTION();
900+
901+
return return_value;
902+
}
903+
"""), generated)
904+
905+
def test_slot_cannot_be_cloned(self):
906+
block = """
907+
/*[clinic input]
908+
class Foo "FooObject *" "&Foo_Type"
909+
[clinic start generated code]*/
910+
/*[clinic input]
911+
Foo.__next__
912+
[clinic start generated code]*/
913+
/*[clinic input]
914+
Foo.other = Foo.__next__
915+
[clinic start generated code]*/
916+
"""
917+
self.expect_failure(block, "Type slot methods cannot be cloned", lineno=8)
918+
810919
def test_getset_in_ifdef(self):
811920
block = """
812921
/*[clinic input]
@@ -3237,6 +3346,78 @@ class Foo "" ""
32373346
f"a normal callable")
32383347
self.expect_failure(block, expected_error, lineno=3)
32393348

3349+
def test_slot_next(self):
3350+
function = self.parse_function("""
3351+
module foo
3352+
class Foo "FooObject *" ""
3353+
Foo.__next__
3354+
""", signatures_in_block=3, function_index=2)
3355+
self.assertIs(function.kind, FunctionKind.SLOT)
3356+
self.assertEqual(function.slot.name, "__next__")
3357+
self.assertEqual(function.c_basename, "Foo_next")
3358+
self.assertEqual(function.return_converter.type, "PyObject *")
3359+
self.assertEqual(list(function.parameters), ["self"])
3360+
3361+
def test_slot_parameters(self):
3362+
slot = SlotSpec('__contains__', 'int', 'int', {
3363+
(Py_ssize_t_converter,): 'sq_fake',
3364+
(object_converter,): 'sq_contains'})
3365+
block = """
3366+
module foo
3367+
class Foo "FooObject *" ""
3368+
Foo.__contains__
3369+
value: {converter}
3370+
/
3371+
"""
3372+
errors = [
3373+
("Foo.__contains__",
3374+
"'__contains__' must declare (Py_ssize_t) for sq_fake or "
3375+
"(object) for sq_contains; got ()"),
3376+
("Foo.__contains__\n value: int\n /",
3377+
"'__contains__' must declare"),
3378+
("Foo.__contains__\n value: object",
3379+
"The parameters of '__contains__' must be positional-only"),
3380+
("Foo.__contains__\n value: object = None\n /",
3381+
"The parameters of '__contains__' cannot have a default value"),
3382+
]
3383+
with mock.patch.dict(SLOTS, {'__contains__': slot}):
3384+
for converter in "Py_ssize_t", "object":
3385+
with self.subTest(converter=converter):
3386+
function = self.parse_function(
3387+
block.format(converter=converter),
3388+
signatures_in_block=3, function_index=2)
3389+
self.assertIs(function.kind, FunctionKind.SLOT)
3390+
self.assertEqual(function.return_converter.type, "int")
3391+
self.assertEqual(list(function.parameters), ["self", "value"])
3392+
for body, err in errors:
3393+
with self.subTest(block=body):
3394+
body = 'class Foo "FooObject *" "&Foo_Type"\n' + body
3395+
self.expect_failure(body, err)
3396+
3397+
def test_slot_errors(self):
3398+
cases = [
3399+
("module foo\nfoo.__next__",
3400+
"'__next__' must be a method!"),
3401+
("@classmethod\nFoo.__next__",
3402+
"'__next__' must be a normal method"),
3403+
("@getter\nFoo.__next__",
3404+
"'__next__' must be a normal method"),
3405+
("@coexist\nFoo.__next__",
3406+
"@coexist cannot be applied to '__next__'"),
3407+
("@vectorcall\nFoo.__next__",
3408+
"@vectorcall can only be used with __init__ and __new__"),
3409+
("Foo.__next__\n a: int",
3410+
"'__next__' methods cannot define parameters"),
3411+
("Foo.__next__ -> int",
3412+
"'Foo.__next__' cannot define a return type"),
3413+
("Foo.__next__\n\nReturn the next item.",
3414+
"'__next__' methods cannot have a docstring"),
3415+
]
3416+
for block, err in cases:
3417+
with self.subTest(block=block):
3418+
block = 'class Foo "FooObject *" "&Foo_Type"\n' + block
3419+
self.expect_failure(block, err)
3420+
32403421
def test_deleter_without_setter(self):
32413422
block = """
32423423
module foo

‎Lib/test/test_getpath.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,74 @@ def test_venv_changed_name_posix(self):
407407
actual = getpath(ns, expected)
408408
self.assertEqual(expected, actual)
409409

410+
def test_venv_relocated_base_installation_posix(self):
411+
"Test a venv whose base installation was relocated from PREFIX."
412+
ns = MockPosixNamespace(
413+
argv0="/venv/bin/python",
414+
PREFIX="/build/install",
415+
)
416+
ns.add_known_xfile("/relocated/bin/python3")
417+
ns.add_known_xfile("/public/bin/python")
418+
ns.add_known_link("/public/bin/python", "/relocated/bin/python3")
419+
ns.add_known_xfile("/venv/bin/python")
420+
ns.add_known_link("/venv/bin/python", "/public/bin/python")
421+
ns.add_known_file("/relocated/lib/python9.8/os.py")
422+
ns.add_known_dir("/relocated/lib/python9.8/lib-dynload")
423+
ns.add_known_file("/venv/pyvenv.cfg", [
424+
"home = /public/bin",
425+
])
426+
expected = dict(
427+
executable="/venv/bin/python",
428+
prefix="/venv",
429+
exec_prefix="/venv",
430+
base_executable="/relocated/bin/python3",
431+
base_prefix="/relocated",
432+
base_exec_prefix="/relocated",
433+
module_search_paths_set=1,
434+
module_search_paths=[
435+
"/relocated/lib/python98.zip",
436+
"/relocated/lib/python9.8",
437+
"/relocated/lib/python9.8/lib-dynload",
438+
],
439+
)
440+
actual = getpath(ns, expected)
441+
self.assertEqual(expected, actual)
442+
443+
def test_venv_symlink_with_misleading_landmarks_posix(self):
444+
"Test that landmarks near an external symlink do effect resolution."
445+
ns = MockPosixNamespace(
446+
argv0="/venv/bin/python",
447+
PREFIX="/opt/python",
448+
)
449+
ns.add_known_xfile("/opt/python/bin/python3")
450+
ns.add_known_xfile("/public/bin/python")
451+
ns.add_known_link("/public/bin/python", "/opt/python/bin/python3")
452+
ns.add_known_xfile("/venv/bin/python")
453+
ns.add_known_link("/venv/bin/python", "/public/bin/python")
454+
ns.add_known_file("/opt/python/lib/python9.8/os.py")
455+
ns.add_known_dir("/opt/python/lib/python9.8/lib-dynload")
456+
ns.add_known_file("/public/lib/python9.8/os.py")
457+
ns.add_known_dir("/public/lib/python9.8/lib-dynload")
458+
ns.add_known_file("/venv/pyvenv.cfg", [
459+
"home = /public/bin",
460+
])
461+
expected = dict(
462+
executable="/venv/bin/python",
463+
prefix="/venv",
464+
exec_prefix="/venv",
465+
base_executable="/opt/python/bin/python3",
466+
base_prefix="/opt/python",
467+
base_exec_prefix="/opt/python",
468+
module_search_paths_set=1,
469+
module_search_paths=[
470+
"/opt/python/lib/python98.zip",
471+
"/opt/python/lib/python9.8",
472+
"/opt/python/lib/python9.8/lib-dynload",
473+
],
474+
)
475+
actual = getpath(ns, expected)
476+
self.assertEqual(expected, actual)
477+
410478
def test_venv_non_installed_zip_path_posix(self):
411479
"Test a venv created from non-installed python has correct zip path."""
412480
ns = MockPosixNamespace(

0 commit comments

Comments
 (0)