summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMatthew Rahtz <matthew.rahtz@gmail.com>2022-03-26 16:55:35 (GMT)
committerGitHub <noreply@github.com>2022-03-26 16:55:35 (GMT)
commite8e737bcf6d22927caebc30c5d57ac4634063219 (patch)
tree6c0d4d51e8216f02fa0e6874aa4d78e20dd9397a /Lib
parent26cca8067bf5306e372c0e90036d832c5021fd90 (diff)
downloadcpython-e8e737bcf6d22927caebc30c5d57ac4634063219.zip
cpython-e8e737bcf6d22927caebc30c5d57ac4634063219.tar.gz
cpython-e8e737bcf6d22927caebc30c5d57ac4634063219.tar.bz2
bpo-43224: Implement PEP 646 grammar changes (GH-31018)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/ast.py9
-rw-r--r--Lib/test/test_ast.py25
-rw-r--r--Lib/test/test_future.py41
-rw-r--r--Lib/test/test_pep646_syntax.py326
-rw-r--r--Lib/test/test_syntax.py143
-rw-r--r--Lib/test/test_unparse.py26
-rw-r--r--Lib/typing.py11
7 files changed, 569 insertions, 12 deletions
diff --git a/Lib/ast.py b/Lib/ast.py
index 625738a..e81e280 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -1476,20 +1476,17 @@ class _Unparser(NodeVisitor):
self.traverse(e)
def visit_Subscript(self, node):
- def is_simple_tuple(slice_value):
- # when unparsing a non-empty tuple, the parentheses can be safely
- # omitted if there aren't any elements that explicitly requires
- # parentheses (such as starred expressions).
+ def is_non_empty_tuple(slice_value):
return (
isinstance(slice_value, Tuple)
and slice_value.elts
- and not any(isinstance(elt, Starred) for elt in slice_value.elts)
)
self.set_precedence(_Precedence.ATOM, node.value)
self.traverse(node.value)
with self.delimit("[", "]"):
- if is_simple_tuple(node.slice):
+ if is_non_empty_tuple(node.slice):
+ # parentheses can be omitted if the tuple isn't empty
self.items_view(self.traverse, node.slice.elts)
else:
self.traverse(node.slice)
diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 039d1c1..03d9b31 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -13,7 +13,7 @@ from textwrap import dedent
from test import support
def to_tuple(t):
- if t is None or isinstance(t, (str, int, complex)):
+ if t is None or isinstance(t, (str, int, complex)) or t is Ellipsis:
return t
elif isinstance(t, list):
return [to_tuple(e) for e in t]
@@ -46,10 +46,20 @@ exec_tests = [
"def f(a=0): pass",
# FunctionDef with varargs
"def f(*args): pass",
+ # FunctionDef with varargs as TypeVarTuple
+ "def f(*args: *Ts): pass",
+ # FunctionDef with varargs as unpacked Tuple
+ "def f(*args: *tuple[int, ...]): pass",
+ # FunctionDef with varargs as unpacked Tuple *and* TypeVarTuple
+ "def f(*args: *tuple[int, *Ts]): pass",
# FunctionDef with kwargs
"def f(**kwargs): pass",
# FunctionDef with all kind of args and docstring
"def f(a, b=1, c=None, d=[], e={}, *args, f=42, **kwargs): 'doc for f()'",
+ # FunctionDef with type annotation on return involving unpacking
+ "def f() -> tuple[*Ts]: pass",
+ "def f() -> tuple[int, *Ts]: pass",
+ "def f() -> tuple[int, *tuple[int, ...]]: pass",
# ClassDef
"class C:pass",
# ClassDef with docstring
@@ -65,6 +75,10 @@ exec_tests = [
"a,b = c",
"(a,b) = c",
"[a,b] = c",
+ # AnnAssign with unpacked types
+ "x: tuple[*Ts]",
+ "x: tuple[int, *Ts]",
+ "x: tuple[int, *tuple[str, ...]]",
# AugAssign
"v += 1",
# For
@@ -2315,8 +2329,14 @@ exec_results = [
('Module', [('FunctionDef', (1, 0, 1, 14), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None)], None, [], [], None, []), [('Pass', (1, 10, 1, 14))], [], None, None)], []),
('Module', [('FunctionDef', (1, 0, 1, 16), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None)], None, [], [], None, [('Constant', (1, 8, 1, 9), 0, None)]), [('Pass', (1, 12, 1, 16))], [], None, None)], []),
('Module', [('FunctionDef', (1, 0, 1, 18), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 11), 'args', None, None), [], [], None, []), [('Pass', (1, 14, 1, 18))], [], None, None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 23), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 16), 'args', ('Starred', (1, 13, 1, 16), ('Name', (1, 14, 1, 16), 'Ts', ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 19, 1, 23))], [], None, None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 36), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 29), 'args', ('Starred', (1, 13, 1, 29), ('Subscript', (1, 14, 1, 29), ('Name', (1, 14, 1, 19), 'tuple', ('Load',)), ('Tuple', (1, 20, 1, 28), [('Name', (1, 20, 1, 23), 'int', ('Load',)), ('Constant', (1, 25, 1, 28), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 32, 1, 36))], [], None, None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 36), 'f', ('arguments', [], [], ('arg', (1, 7, 1, 29), 'args', ('Starred', (1, 13, 1, 29), ('Subscript', (1, 14, 1, 29), ('Name', (1, 14, 1, 19), 'tuple', ('Load',)), ('Tuple', (1, 20, 1, 28), [('Name', (1, 20, 1, 23), 'int', ('Load',)), ('Starred', (1, 25, 1, 28), ('Name', (1, 26, 1, 28), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), ('Load',)), None), [], [], None, []), [('Pass', (1, 32, 1, 36))], [], None, None)], []),
('Module', [('FunctionDef', (1, 0, 1, 21), 'f', ('arguments', [], [], None, [], [], ('arg', (1, 8, 1, 14), 'kwargs', None, None), []), [('Pass', (1, 17, 1, 21))], [], None, None)], []),
('Module', [('FunctionDef', (1, 0, 1, 71), 'f', ('arguments', [], [('arg', (1, 6, 1, 7), 'a', None, None), ('arg', (1, 9, 1, 10), 'b', None, None), ('arg', (1, 14, 1, 15), 'c', None, None), ('arg', (1, 22, 1, 23), 'd', None, None), ('arg', (1, 28, 1, 29), 'e', None, None)], ('arg', (1, 35, 1, 39), 'args', None, None), [('arg', (1, 41, 1, 42), 'f', None, None)], [('Constant', (1, 43, 1, 45), 42, None)], ('arg', (1, 49, 1, 55), 'kwargs', None, None), [('Constant', (1, 11, 1, 12), 1, None), ('Constant', (1, 16, 1, 20), None, None), ('List', (1, 24, 1, 26), [], ('Load',)), ('Dict', (1, 30, 1, 32), [], [])]), [('Expr', (1, 58, 1, 71), ('Constant', (1, 58, 1, 71), 'doc for f()', None))], [], None, None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 27), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 23, 1, 27))], [], ('Subscript', (1, 11, 1, 21), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 20), [('Starred', (1, 17, 1, 20), ('Name', (1, 18, 1, 20), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 32), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 28, 1, 32))], [], ('Subscript', (1, 11, 1, 26), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 25), [('Name', (1, 17, 1, 20), 'int', ('Load',)), ('Starred', (1, 22, 1, 25), ('Name', (1, 23, 1, 25), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
+('Module', [('FunctionDef', (1, 0, 1, 45), 'f', ('arguments', [], [], None, [], [], None, []), [('Pass', (1, 41, 1, 45))], [], ('Subscript', (1, 11, 1, 39), ('Name', (1, 11, 1, 16), 'tuple', ('Load',)), ('Tuple', (1, 17, 1, 38), [('Name', (1, 17, 1, 20), 'int', ('Load',)), ('Starred', (1, 22, 1, 38), ('Subscript', (1, 23, 1, 38), ('Name', (1, 23, 1, 28), 'tuple', ('Load',)), ('Tuple', (1, 29, 1, 37), [('Name', (1, 29, 1, 32), 'int', ('Load',)), ('Constant', (1, 34, 1, 37), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',))], ('Load',)), ('Load',)), None)], []),
('Module', [('ClassDef', (1, 0, 1, 12), 'C', [], [], [('Pass', (1, 8, 1, 12))], [])], []),
('Module', [('ClassDef', (1, 0, 1, 32), 'C', [], [], [('Expr', (1, 9, 1, 32), ('Constant', (1, 9, 1, 32), 'docstring for class C', None))], [])], []),
('Module', [('ClassDef', (1, 0, 1, 21), 'C', [('Name', (1, 8, 1, 14), 'object', ('Load',))], [], [('Pass', (1, 17, 1, 21))], [])], []),
@@ -2326,6 +2346,9 @@ exec_results = [
('Module', [('Assign', (1, 0, 1, 7), [('Tuple', (1, 0, 1, 3), [('Name', (1, 0, 1, 1), 'a', ('Store',)), ('Name', (1, 2, 1, 3), 'b', ('Store',))], ('Store',))], ('Name', (1, 6, 1, 7), 'c', ('Load',)), None)], []),
('Module', [('Assign', (1, 0, 1, 9), [('Tuple', (1, 0, 1, 5), [('Name', (1, 1, 1, 2), 'a', ('Store',)), ('Name', (1, 3, 1, 4), 'b', ('Store',))], ('Store',))], ('Name', (1, 8, 1, 9), 'c', ('Load',)), None)], []),
('Module', [('Assign', (1, 0, 1, 9), [('List', (1, 0, 1, 5), [('Name', (1, 1, 1, 2), 'a', ('Store',)), ('Name', (1, 3, 1, 4), 'b', ('Store',))], ('Store',))], ('Name', (1, 8, 1, 9), 'c', ('Load',)), None)], []),
+('Module', [('AnnAssign', (1, 0, 1, 13), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 13), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 12), [('Starred', (1, 9, 1, 12), ('Name', (1, 10, 1, 12), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
+('Module', [('AnnAssign', (1, 0, 1, 18), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 18), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 17), [('Name', (1, 9, 1, 12), 'int', ('Load',)), ('Starred', (1, 14, 1, 17), ('Name', (1, 15, 1, 17), 'Ts', ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
+('Module', [('AnnAssign', (1, 0, 1, 31), ('Name', (1, 0, 1, 1), 'x', ('Store',)), ('Subscript', (1, 3, 1, 31), ('Name', (1, 3, 1, 8), 'tuple', ('Load',)), ('Tuple', (1, 9, 1, 30), [('Name', (1, 9, 1, 12), 'int', ('Load',)), ('Starred', (1, 14, 1, 30), ('Subscript', (1, 15, 1, 30), ('Name', (1, 15, 1, 20), 'tuple', ('Load',)), ('Tuple', (1, 21, 1, 29), [('Name', (1, 21, 1, 24), 'str', ('Load',)), ('Constant', (1, 26, 1, 29), Ellipsis, None)], ('Load',)), ('Load',)), ('Load',))], ('Load',)), ('Load',)), None, 1)], []),
('Module', [('AugAssign', (1, 0, 1, 6), ('Name', (1, 0, 1, 1), 'v', ('Store',)), ('Add',), ('Constant', (1, 5, 1, 6), 1, None))], []),
('Module', [('For', (1, 0, 1, 15), ('Name', (1, 4, 1, 5), 'v', ('Store',)), ('Name', (1, 9, 1, 10), 'v', ('Load',)), [('Pass', (1, 11, 1, 15))], [], None)], []),
('Module', [('While', (1, 0, 1, 12), ('Name', (1, 6, 1, 7), 'v', ('Load',)), [('Pass', (1, 8, 1, 12))], [])], []),
diff --git a/Lib/test/test_future.py b/Lib/test/test_future.py
index 5a3944e..c3e8420 100644
--- a/Lib/test/test_future.py
+++ b/Lib/test/test_future.py
@@ -175,7 +175,7 @@ class AnnotationsFutureTestCase(unittest.TestCase):
scope = {}
exec(
"from __future__ import annotations\n"
- + code, {}, scope
+ + code, scope
)
return scope
@@ -287,10 +287,11 @@ class AnnotationsFutureTestCase(unittest.TestCase):
eq("list[str]")
eq("dict[str, int]")
eq("set[str,]")
+ eq("tuple[()]")
eq("tuple[str, ...]")
- eq("tuple[(str, *types)]")
+ eq("tuple[str, *types]")
eq("tuple[str, int, (str, int)]")
- eq("tuple[(*int, str, str, (str, int))]")
+ eq("tuple[*int, str, str, (str, int)]")
eq("tuple[str, int, float, dict[str, int]]")
eq("slice[0]")
eq("slice[0:1]")
@@ -305,6 +306,21 @@ class AnnotationsFutureTestCase(unittest.TestCase):
eq("slice[1:2, 1]")
eq("slice[1:2, 2, 3]")
eq("slice[()]")
+ # Note that `slice[*Ts]`, `slice[*Ts,]`, and `slice[(*Ts,)]` all have
+ # the same AST, but only `slice[*Ts,]` passes this test, because that's
+ # what the unparser produces.
+ eq("slice[*Ts,]")
+ eq("slice[1, *Ts]")
+ eq("slice[*Ts, 2]")
+ eq("slice[1, *Ts, 2]")
+ eq("slice[*Ts, *Ts]")
+ eq("slice[1, *Ts, *Ts]")
+ eq("slice[*Ts, 1, *Ts]")
+ eq("slice[*Ts, *Ts, 1]")
+ eq("slice[1, *Ts, *Ts, 2]")
+ eq("slice[1:2, *Ts]")
+ eq("slice[*Ts, 1:2]")
+ eq("slice[1:2, *Ts, 3:4]")
eq("slice[a, b:c, d:e:f]")
eq("slice[(x for x in a)]")
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
@@ -403,6 +419,25 @@ class AnnotationsFutureTestCase(unittest.TestCase):
def bar(arg: (yield)): pass
"""))
+ def test_get_type_hints_on_func_with_variadic_arg(self):
+ # `typing.get_type_hints` might break on a function with a variadic
+ # annotation (e.g. `f(*args: *Ts)`) if `from __future__ import
+ # annotations`, because it could try to evaluate `*Ts` as an expression,
+ # which on its own isn't value syntax.
+ namespace = self._exec_future(dedent("""\
+ class StarredC: pass
+ class C:
+ def __iter__(self):
+ yield StarredC()
+ c = C()
+ def f(*args: *c): pass
+ import typing
+ hints = typing.get_type_hints(f)
+ """))
+
+ hints = namespace.pop('hints')
+ self.assertIsInstance(hints['args'], namespace['StarredC'])
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_pep646_syntax.py b/Lib/test/test_pep646_syntax.py
new file mode 100644
index 0000000..3ffa82d
--- /dev/null
+++ b/Lib/test/test_pep646_syntax.py
@@ -0,0 +1,326 @@
+doctests = """
+
+Setup
+
+ >>> class AClass:
+ ... def __init__(self):
+ ... self._setitem_name = None
+ ... self._setitem_val = None
+ ... self._delitem_name = None
+ ... def __setitem__(self, name, val):
+ ... self._delitem_name = None
+ ... self._setitem_name = name
+ ... self._setitem_val = val
+ ... def __repr__(self):
+ ... if self._setitem_name is not None:
+ ... return f"A[{self._setitem_name}]={self._setitem_val}"
+ ... elif self._delitem_name is not None:
+ ... return f"delA[{self._delitem_name}]"
+ ... def __getitem__(self, name):
+ ... return ParameterisedA(name)
+ ... def __delitem__(self, name):
+ ... self._setitem_name = None
+ ... self._delitem_name = name
+ ...
+ >>> class ParameterisedA:
+ ... def __init__(self, name):
+ ... self._name = name
+ ... def __repr__(self):
+ ... return f"A[{self._name}]"
+ ... def __iter__(self):
+ ... for p in self._name:
+ ... yield p
+ >>> class B:
+ ... def __iter__(self):
+ ... yield StarredB()
+ ... def __repr__(self):
+ ... return "B"
+ >>> class StarredB:
+ ... def __repr__(self):
+ ... return "StarredB"
+ >>> A = AClass()
+ >>> b = B()
+
+Slices that are supposed to work, starring our custom B class
+
+ >>> A[*b]
+ A[(StarredB,)]
+ >>> A[*b] = 1; A
+ A[(StarredB,)]=1
+ >>> del A[*b]; A
+ delA[(StarredB,)]
+
+ >>> A[*b, *b]
+ A[(StarredB, StarredB)]
+ >>> A[*b, *b] = 1; A
+ A[(StarredB, StarredB)]=1
+ >>> del A[*b, *b]; A
+ delA[(StarredB, StarredB)]
+
+ >>> A[b, *b]
+ A[(B, StarredB)]
+ >>> A[b, *b] = 1; A
+ A[(B, StarredB)]=1
+ >>> del A[b, *b]; A
+ delA[(B, StarredB)]
+
+ >>> A[*b, b]
+ A[(StarredB, B)]
+ >>> A[*b, b] = 1; A
+ A[(StarredB, B)]=1
+ >>> del A[*b, b]; A
+ delA[(StarredB, B)]
+
+ >>> A[b, b, *b]
+ A[(B, B, StarredB)]
+ >>> A[b, b, *b] = 1; A
+ A[(B, B, StarredB)]=1
+ >>> del A[b, b, *b]; A
+ delA[(B, B, StarredB)]
+
+ >>> A[*b, b, b]
+ A[(StarredB, B, B)]
+ >>> A[*b, b, b] = 1; A
+ A[(StarredB, B, B)]=1
+ >>> del A[*b, b, b]; A
+ delA[(StarredB, B, B)]
+
+ >>> A[b, *b, b]
+ A[(B, StarredB, B)]
+ >>> A[b, *b, b] = 1; A
+ A[(B, StarredB, B)]=1
+ >>> del A[b, *b, b]; A
+ delA[(B, StarredB, B)]
+
+ >>> A[b, b, *b, b]
+ A[(B, B, StarredB, B)]
+ >>> A[b, b, *b, b] = 1; A
+ A[(B, B, StarredB, B)]=1
+ >>> del A[b, b, *b, b]; A
+ delA[(B, B, StarredB, B)]
+
+ >>> A[b, *b, b, b]
+ A[(B, StarredB, B, B)]
+ >>> A[b, *b, b, b] = 1; A
+ A[(B, StarredB, B, B)]=1
+ >>> del A[b, *b, b, b]; A
+ delA[(B, StarredB, B, B)]
+
+ >>> A[A[b, *b, b]]
+ A[A[(B, StarredB, B)]]
+ >>> A[A[b, *b, b]] = 1; A
+ A[A[(B, StarredB, B)]]=1
+ >>> del A[A[b, *b, b]]; A
+ delA[A[(B, StarredB, B)]]
+
+ >>> A[*A[b, *b, b]]
+ A[(B, StarredB, B)]
+ >>> A[*A[b, *b, b]] = 1; A
+ A[(B, StarredB, B)]=1
+ >>> del A[*A[b, *b, b]]; A
+ delA[(B, StarredB, B)]
+
+ >>> A[b, ...]
+ A[(B, Ellipsis)]
+ >>> A[b, ...] = 1; A
+ A[(B, Ellipsis)]=1
+ >>> del A[b, ...]; A
+ delA[(B, Ellipsis)]
+
+ >>> A[*A[b, ...]]
+ A[(B, Ellipsis)]
+ >>> A[*A[b, ...]] = 1; A
+ A[(B, Ellipsis)]=1
+ >>> del A[*A[b, ...]]; A
+ delA[(B, Ellipsis)]
+
+Slices that are supposed to work, starring a list
+
+ >>> l = [1, 2, 3]
+
+ >>> A[*l]
+ A[(1, 2, 3)]
+ >>> A[*l] = 1; A
+ A[(1, 2, 3)]=1
+ >>> del A[*l]; A
+ delA[(1, 2, 3)]
+
+ >>> A[*l, 4]
+ A[(1, 2, 3, 4)]
+ >>> A[*l, 4] = 1; A
+ A[(1, 2, 3, 4)]=1
+ >>> del A[*l, 4]; A
+ delA[(1, 2, 3, 4)]
+
+ >>> A[0, *l]
+ A[(0, 1, 2, 3)]
+ >>> A[0, *l] = 1; A
+ A[(0, 1, 2, 3)]=1
+ >>> del A[0, *l]; A
+ delA[(0, 1, 2, 3)]
+
+ >>> A[1:2, *l]
+ A[(slice(1, 2, None), 1, 2, 3)]
+ >>> A[1:2, *l] = 1; A
+ A[(slice(1, 2, None), 1, 2, 3)]=1
+ >>> del A[1:2, *l]; A
+ delA[(slice(1, 2, None), 1, 2, 3)]
+
+ >>> repr(A[1:2, *l]) == repr(A[1:2, 1, 2, 3])
+ True
+
+Slices that are supposed to work, starring a tuple
+
+ >>> t = (1, 2, 3)
+
+ >>> A[*t]
+ A[(1, 2, 3)]
+ >>> A[*t] = 1; A
+ A[(1, 2, 3)]=1
+ >>> del A[*t]; A
+ delA[(1, 2, 3)]
+
+ >>> A[*t, 4]
+ A[(1, 2, 3, 4)]
+ >>> A[*t, 4] = 1; A
+ A[(1, 2, 3, 4)]=1
+ >>> del A[*t, 4]; A
+ delA[(1, 2, 3, 4)]
+
+ >>> A[0, *t]
+ A[(0, 1, 2, 3)]
+ >>> A[0, *t] = 1; A
+ A[(0, 1, 2, 3)]=1
+ >>> del A[0, *t]; A
+ delA[(0, 1, 2, 3)]
+
+ >>> A[1:2, *t]
+ A[(slice(1, 2, None), 1, 2, 3)]
+ >>> A[1:2, *t] = 1; A
+ A[(slice(1, 2, None), 1, 2, 3)]=1
+ >>> del A[1:2, *t]; A
+ delA[(slice(1, 2, None), 1, 2, 3)]
+
+ >>> repr(A[1:2, *t]) == repr(A[1:2, 1, 2, 3])
+ True
+
+Starring an expression (rather than a name) in a slice
+
+ >>> def returns_list():
+ ... return [1, 2, 3]
+
+ >>> A[returns_list()]
+ A[[1, 2, 3]]
+ >>> A[returns_list()] = 1; A
+ A[[1, 2, 3]]=1
+ >>> del A[returns_list()]; A
+ delA[[1, 2, 3]]
+
+ >>> A[returns_list(), 4]
+ A[([1, 2, 3], 4)]
+ >>> A[returns_list(), 4] = 1; A
+ A[([1, 2, 3], 4)]=1
+ >>> del A[returns_list(), 4]; A
+ delA[([1, 2, 3], 4)]
+
+ >>> A[*returns_list()]
+ A[(1, 2, 3)]
+ >>> A[*returns_list()] = 1; A
+ A[(1, 2, 3)]=1
+ >>> del A[*returns_list()]; A
+ delA[(1, 2, 3)]
+
+ >>> A[*returns_list(), 4]
+ A[(1, 2, 3, 4)]
+ >>> A[*returns_list(), 4] = 1; A
+ A[(1, 2, 3, 4)]=1
+ >>> del A[*returns_list(), 4]; A
+ delA[(1, 2, 3, 4)]
+
+ >>> A[0, *returns_list()]
+ A[(0, 1, 2, 3)]
+ >>> A[0, *returns_list()] = 1; A
+ A[(0, 1, 2, 3)]=1
+ >>> del A[0, *returns_list()]; A
+ delA[(0, 1, 2, 3)]
+
+ >>> A[*returns_list(), *returns_list()]
+ A[(1, 2, 3, 1, 2, 3)]
+ >>> A[*returns_list(), *returns_list()] = 1; A
+ A[(1, 2, 3, 1, 2, 3)]=1
+ >>> del A[*returns_list(), *returns_list()]; A
+ delA[(1, 2, 3, 1, 2, 3)]
+
+Using both a starred object and a start:stop in a slice
+(See also tests in test_syntax confirming that starring *inside* a start:stop
+is *not* valid syntax.)
+
+ >>> A[1:2, *b]
+ A[(slice(1, 2, None), StarredB)]
+ >>> A[*b, 1:2]
+ A[(StarredB, slice(1, 2, None))]
+ >>> A[1:2, *b, 1:2]
+ A[(slice(1, 2, None), StarredB, slice(1, 2, None))]
+ >>> A[*b, 1:2, *b]
+ A[(StarredB, slice(1, 2, None), StarredB)]
+
+ >>> A[1:, *b]
+ A[(slice(1, None, None), StarredB)]
+ >>> A[*b, 1:]
+ A[(StarredB, slice(1, None, None))]
+ >>> A[1:, *b, 1:]
+ A[(slice(1, None, None), StarredB, slice(1, None, None))]
+ >>> A[*b, 1:, *b]
+ A[(StarredB, slice(1, None, None), StarredB)]
+
+ >>> A[:1, *b]
+ A[(slice(None, 1, None), StarredB)]
+ >>> A[*b, :1]
+ A[(StarredB, slice(None, 1, None))]
+ >>> A[:1, *b, :1]
+ A[(slice(None, 1, None), StarredB, slice(None, 1, None))]
+ >>> A[*b, :1, *b]
+ A[(StarredB, slice(None, 1, None), StarredB)]
+
+ >>> A[:, *b]
+ A[(slice(None, None, None), StarredB)]
+ >>> A[*b, :]
+ A[(StarredB, slice(None, None, None))]
+ >>> A[:, *b, :]
+ A[(slice(None, None, None), StarredB, slice(None, None, None))]
+ >>> A[*b, :, *b]
+ A[(StarredB, slice(None, None, None), StarredB)]
+
+*args annotated as starred expression
+
+ >>> def f1(*args: *b): pass
+ >>> f1.__annotations__
+ {'args': StarredB}
+
+ >>> def f2(*args: *b, arg1): pass
+ >>> f2.__annotations__
+ {'args': StarredB}
+
+ >>> def f3(*args: *b, arg1: int): pass
+ >>> f3.__annotations__
+ {'args': StarredB, 'arg1': <class 'int'>}
+
+ >>> def f4(*args: *b, arg1: int = 2): pass
+ >>> f4.__annotations__
+ {'args': StarredB, 'arg1': <class 'int'>}
+
+ >>> def f5(*args: *b = (1,)): pass
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+"""
+
+__test__ = {'doctests' : doctests}
+
+def test_main(verbose=False):
+ from test import support
+ from test import test_pep646_syntax
+ support.run_doctest(test_pep646_syntax, verbose)
+
+if __name__ == "__main__":
+ test_main(verbose=True)
diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py
index 5f16a15..3e79ebf 100644
--- a/Lib/test/test_syntax.py
+++ b/Lib/test/test_syntax.py
@@ -1622,6 +1622,149 @@ Corner-cases that used to crash:
... ...
Traceback (most recent call last):
SyntaxError: positional patterns follow keyword patterns
+
+Uses of the star operator which should fail:
+
+A[:*b]
+
+ >>> A[:*b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[:(*b)]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: cannot use starred expression here
+ >>> A[:*b] = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> del A[:*b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[*b:]
+
+ >>> A[*b:]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[(*b):]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: cannot use starred expression here
+ >>> A[*b:] = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> del A[*b:]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[*b:*b]
+
+ >>> A[*b:*b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[(*b:*b)]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[*b:*b] = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> del A[*b:*b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[*(1:2)]
+
+ >>> A[*(1:2)]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[*(1:2)] = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> del A[*(1:2)]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[*:] and A[:*]
+
+ >>> A[*:]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[:*]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[*]
+
+ >>> A[*]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[**]
+
+ >>> A[**]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+A[**b]
+
+ >>> A[**b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> A[**b] = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> del A[**b]
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+def f(x: *b)
+
+ >>> def f6(x: *b): pass
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> def f7(x: *b = 1): pass
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+**kwargs: *a
+
+ >>> def f8(**kwargs: *a): pass
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+
+x: *b
+
+ >>> x: *b
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
+ >>> x: *b = 1
+ Traceback (most recent call last):
+ ...
+ SyntaxError: invalid syntax
"""
import re
diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py
index f5be13a..e38b335 100644
--- a/Lib/test/test_unparse.py
+++ b/Lib/test/test_unparse.py
@@ -344,7 +344,17 @@ class UnparseTestCase(ASTTestCase):
self.check_ast_roundtrip("a[i]")
self.check_ast_roundtrip("a[i,]")
self.check_ast_roundtrip("a[i, j]")
+ # The AST for these next two both look like `a[(*a,)]`
self.check_ast_roundtrip("a[(*a,)]")
+ self.check_ast_roundtrip("a[*a]")
+ self.check_ast_roundtrip("a[b, *a]")
+ self.check_ast_roundtrip("a[*a, c]")
+ self.check_ast_roundtrip("a[b, *a, c]")
+ self.check_ast_roundtrip("a[*a, *a]")
+ self.check_ast_roundtrip("a[b, *a, *a]")
+ self.check_ast_roundtrip("a[*a, b, *a]")
+ self.check_ast_roundtrip("a[*a, *a, b]")
+ self.check_ast_roundtrip("a[b, *a, *a, c]")
self.check_ast_roundtrip("a[(a:=b)]")
self.check_ast_roundtrip("a[(a:=b,c)]")
self.check_ast_roundtrip("a[()]")
@@ -543,9 +553,23 @@ class CosmeticTestCase(ASTTestCase):
self.check_src_roundtrip(f"{prefix} 1")
def test_slices(self):
+ self.check_src_roundtrip("a[()]")
self.check_src_roundtrip("a[1]")
self.check_src_roundtrip("a[1, 2]")
- self.check_src_roundtrip("a[(1, *a)]")
+ # Note that `a[*a]`, `a[*a,]`, and `a[(*a,)]` all evaluate to the same
+ # thing at runtime and have the same AST, but only `a[*a,]` passes
+ # this test, because that's what `ast.unparse` produces.
+ self.check_src_roundtrip("a[*a,]")
+ self.check_src_roundtrip("a[1, *a]")
+ self.check_src_roundtrip("a[*a, 2]")
+ self.check_src_roundtrip("a[1, *a, 2]")
+ self.check_src_roundtrip("a[*a, *a]")
+ self.check_src_roundtrip("a[1, *a, *a]")
+ self.check_src_roundtrip("a[*a, 1, *a]")
+ self.check_src_roundtrip("a[*a, *a, 1]")
+ self.check_src_roundtrip("a[1, *a, *a, 2]")
+ self.check_src_roundtrip("a[1:2, *a]")
+ self.check_src_roundtrip("a[*a, 1:2]")
def test_lambda_parameters(self):
self.check_src_roundtrip("lambda: something")
diff --git a/Lib/typing.py b/Lib/typing.py
index 64b348e..36f9ece 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -734,10 +734,19 @@ class ForwardRef(_Final, _root=True):
def __init__(self, arg, is_argument=True, module=None, *, is_class=False):
if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
+
+ # If we do `def f(*args: *Ts)`, then we'll have `arg = '*Ts'`.
+ # Unfortunately, this isn't a valid expression on its own, so we
+ # do the unpacking manually.
+ if arg[0] == '*':
+ arg_to_compile = f'({arg},)[0]' # E.g. (*Ts,)[0]
+ else:
+ arg_to_compile = arg
try:
- code = compile(arg, '<string>', 'eval')
+ code = compile(arg_to_compile, '<string>', 'eval')
except SyntaxError:
raise SyntaxError(f"Forward reference must be an expression -- got {arg!r}")
+
self.__forward_arg__ = arg
self.__forward_code__ = code
self.__forward_evaluated__ = False