Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit be501ca

Browse files
authored
bpo-39702: Relax grammar restrictions on decorators (PEP 614) (GH-18570)
1 parent 116fd4a commit be501ca

File tree

8 files changed

+534
-537
lines changed

8 files changed

+534
-537
lines changed

Grammar/Grammar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
1414
file_input: (NEWLINE | stmt)* ENDMARKER
1515
eval_input: testlist NEWLINE* ENDMARKER
1616

17-
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
17+
decorator: '@' namedexpr_test NEWLINE
1818
decorators: decorator+
1919
decorated: decorators (classdef | funcdef | async_funcdef)
2020

Include/node.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ PyAPI_FUNC(Py_ssize_t) _PyNode_SizeOf(node *n);
3131
#define NCH(n) ((n)->n_nchildren)
3232

3333
#define CHILD(n, i) (&(n)->n_child[i])
34-
#define RCHILD(n, i) (CHILD(n, NCH(n) + i))
3534
#define TYPE(n) ((n)->n_type)
3635
#define STR(n) ((n)->n_str)
3736
#define LINENO(n) ((n)->n_lineno)

Lib/test/test_decorators.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -151,21 +151,18 @@ def double(x):
151151
self.assertEqual(counts['double'], 4)
152152

153153
def test_errors(self):
154-
# Test syntax restrictions - these are all compile-time errors:
155-
#
156-
for expr in [ "1+2", "x[3]", "(1, 2)" ]:
157-
# Sanity check: is expr is a valid expression by itself?
158-
compile(expr, "testexpr", "exec")
159-
160-
codestr = "@%s\ndef f(): pass" % expr
161-
self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
162154

163-
# You can't put multiple decorators on a single line:
164-
#
165-
self.assertRaises(SyntaxError, compile,
166-
"@f1 @f2\ndef f(): pass", "test", "exec")
155+
# Test SyntaxErrors:
156+
for stmt in ("x,", "x, y", "x = y", "pass", "import sys"):
157+
compile(stmt, "test", "exec") # Sanity check.
158+
with self.assertRaises(SyntaxError):
159+
compile(f"@{stmt}\ndef f(): pass", "test", "exec")
167160

168-
# Test runtime errors
161+
# Test TypeErrors that used to be SyntaxErrors:
162+
for expr in ("1.+2j", "[1, 2][-1]", "(1, 2)", "True", "...", "None"):
163+
compile(expr, "test", "eval") # Sanity check.
164+
with self.assertRaises(TypeError):
165+
exec(f"@{expr}\ndef f(): pass")
169166

170167
def unimp(func):
171168
raise NotImplementedError
@@ -179,6 +176,13 @@ def unimp(func):
179176
code = compile(codestr, "test", "exec")
180177
self.assertRaises(exc, eval, code, context)
181178

179+
def test_expressions(self):
180+
for expr in (
181+
"(x,)", "(x, y)", "x := y", "(x := y)", "x @y", "(x @ y)", "x[0]",
182+
"w[x].y.z", "w + x - (y + z)", "x(y)()(z)", "[w, x, y][z]", "x.y",
183+
):
184+
compile(f"@{expr}\ndef f(): pass", "test", "exec")
185+
182186
def test_double(self):
183187
class C(object):
184188
@funcattrs(abc=1, xyz="haha")

Lib/test/test_grammar.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def test_var_annot_rhs(self):
460460

461461
def test_funcdef(self):
462462
### [decorators] 'def' NAME parameters ['->' test] ':' suite
463-
### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
463+
### decorator: '@' namedexpr_test NEWLINE
464464
### decorators: decorator+
465465
### parameters: '(' [typedargslist] ')'
466466
### typedargslist: ((tfpdef ['=' test] ',')*
@@ -666,6 +666,20 @@ def null(x): return x
666666
def f(x) -> list: pass
667667
self.assertEqual(f.__annotations__, {'return': list})
668668

669+
# Test expressions as decorators (PEP 614):
670+
@False or null
671+
def f(x): pass
672+
@d := null
673+
def f(x): pass
674+
@lambda f: null(f)
675+
def f(x): pass
676+
@[..., null, ...][1]
677+
def f(x): pass
678+
@null(null)(null)
679+
def f(x): pass
680+
@[null][0].__call__.__call__
681+
def f(x): pass
682+
669683
# test closures with a variety of opargs
670684
closure = 1
671685
def f(): return closure
@@ -1515,13 +1529,27 @@ def meth1(self): pass
15151529
def meth2(self, arg): pass
15161530
def meth3(self, a1, a2): pass
15171531

1518-
# decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
1532+
# decorator: '@' namedexpr_test NEWLINE
15191533
# decorators: decorator+
15201534
# decorated: decorators (classdef | funcdef)
15211535
def class_decorator(x): return x
15221536
@class_decorator
15231537
class G: pass
15241538

1539+
# Test expressions as decorators (PEP 614):
1540+
@False or class_decorator
1541+
class H: pass
1542+
@d := class_decorator
1543+
class I: pass
1544+
@lambda c: class_decorator(c)
1545+
class J: pass
1546+
@[..., class_decorator, ...][1]
1547+
class K: pass
1548+
@class_decorator(class_decorator)(class_decorator)
1549+
class L: pass
1550+
@[class_decorator][0].__call__.__call__
1551+
class M: pass
1552+
15251553
def test_dictcomps(self):
15261554
# dictorsetmaker: ( (test ':' test (comp_for |
15271555
# (',' test ':' test)* [','])) |

Lib/test/test_parser.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,27 @@ def test_function_defs(self):
226226
self.check_suite("@funcattrs()\n"
227227
"def f(): pass")
228228

229+
self.check_suite("@False or x\n"
230+
"def f(): pass")
231+
self.check_suite("@d := x\n"
232+
"def f(): pass")
233+
self.check_suite("@lambda f: x(f)\n"
234+
"def f(): pass")
235+
self.check_suite("@[..., x, ...][1]\n"
236+
"def f(): pass")
237+
self.check_suite("@x(x)(x)\n"
238+
"def f(): pass")
239+
self.check_suite("@(x, x)\n"
240+
"def f(): pass")
241+
self.check_suite("@...\n"
242+
"def f(): pass")
243+
self.check_suite("@None\n"
244+
"def f(): pass")
245+
self.check_suite("@w @(x @y) @(z)\n"
246+
"def f(): pass")
247+
self.check_suite("@w[x].y.z\n"
248+
"def f(): pass")
249+
229250
# keyword-only arguments
230251
self.check_suite("def f(*, a): pass")
231252
self.check_suite("def f(*, a = 5): pass")
@@ -270,6 +291,27 @@ def test_class_defs(self):
270291
"@decorator2\n"
271292
"class foo():pass")
272293

294+
self.check_suite("@False or x\n"
295+
"class C: pass")
296+
self.check_suite("@d := x\n"
297+
"class C: pass")
298+
self.check_suite("@lambda f: x(f)\n"
299+
"class C: pass")
300+
self.check_suite("@[..., x, ...][1]\n"
301+
"class C: pass")
302+
self.check_suite("@x(x)(x)\n"
303+
"class C: pass")
304+
self.check_suite("@(x, x)\n"
305+
"class C: pass")
306+
self.check_suite("@...\n"
307+
"class C: pass")
308+
self.check_suite("@None\n"
309+
"class C: pass")
310+
self.check_suite("@w @(x @y) @(z)\n"
311+
"class C: pass")
312+
self.check_suite("@w[x].y.z\n"
313+
"class C: pass")
314+
273315
def test_import_from_statement(self):
274316
self.check_suite("from sys.path import *")
275317
self.check_suite("from sys.path import dirname")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Relax :term:`decorator` grammar restrictions to allow any valid expression
2+
(:pep:`614`).

Python/ast.c

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,80 +1691,16 @@ ast_for_arguments(struct compiling *c, const node *n)
16911691
return arguments(posonlyargs, posargs, vararg, kwonlyargs, kwdefaults, kwarg, posdefaults, c->c_arena);
16921692
}
16931693

1694-
static expr_ty
1695-
ast_for_dotted_name(struct compiling *c, const node *n)
1696-
{
1697-
expr_ty e;
1698-
identifier id;
1699-
int lineno, col_offset;
1700-
int i;
1701-
node *ch;
1702-
1703-
REQ(n, dotted_name);
1704-
1705-
lineno = LINENO(n);
1706-
col_offset = n->n_col_offset;
1707-
1708-
ch = CHILD(n, 0);
1709-
id = NEW_IDENTIFIER(ch);
1710-
if (!id)
1711-
return NULL;
1712-
e = Name(id, Load, lineno, col_offset,
1713-
ch->n_end_lineno, ch->n_end_col_offset, c->c_arena);
1714-
if (!e)
1715-
return NULL;
1716-
1717-
for (i = 2; i < NCH(n); i+=2) {
1718-
const node *child = CHILD(n, i);
1719-
id = NEW_IDENTIFIER(child);
1720-
if (!id)
1721-
return NULL;
1722-
e = Attribute(e, id, Load, lineno, col_offset,
1723-
child->n_end_lineno, child->n_end_col_offset, c->c_arena);
1724-
if (!e)
1725-
return NULL;
1726-
}
1727-
1728-
return e;
1729-
}
1730-
17311694
static expr_ty
17321695
ast_for_decorator(struct compiling *c, const node *n)
17331696
{
1734-
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE */
1735-
expr_ty d = NULL;
1736-
expr_ty name_expr;
1697+
/* decorator: '@' namedexpr_test NEWLINE */
17371698

17381699
REQ(n, decorator);
17391700
REQ(CHILD(n, 0), AT);
1740-
REQ(RCHILD(n, -1), NEWLINE);
1741-
1742-
name_expr = ast_for_dotted_name(c, CHILD(n, 1));
1743-
if (!name_expr)
1744-
return NULL;
1745-
1746-
if (NCH(n) == 3) { /* No arguments */
1747-
d = name_expr;
1748-
name_expr = NULL;
1749-
}
1750-
else if (NCH(n) == 5) { /* Call with no arguments */
1751-
d = Call(name_expr, NULL, NULL,
1752-
name_expr->lineno, name_expr->col_offset,
1753-
CHILD(n, 3)->n_end_lineno, CHILD(n, 3)->n_end_col_offset,
1754-
c->c_arena);
1755-
if (!d)
1756-
return NULL;
1757-
name_expr = NULL;
1758-
}
1759-
else {
1760-
d = ast_for_call(c, CHILD(n, 3), name_expr,
1761-
CHILD(n, 1), CHILD(n, 2), CHILD(n, 4));
1762-
if (!d)
1763-
return NULL;
1764-
name_expr = NULL;
1765-
}
1766-
1767-
return d;
1701+
REQ(CHILD(n, 2), NEWLINE);
1702+
1703+
return ast_for_expr(c, CHILD(n, 1));
17681704
}
17691705

17701706
static asdl_seq*

0 commit comments

Comments
 (0)