bpo-39702: Relax grammar restrictions on decorators (PEP 614) (GH-18570)
This commit is contained in:
parent
116fd4af73
commit
be501ca241
|
@ -14,7 +14,7 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||||
file_input: (NEWLINE | stmt)* ENDMARKER
|
file_input: (NEWLINE | stmt)* ENDMARKER
|
||||||
eval_input: testlist NEWLINE* ENDMARKER
|
eval_input: testlist NEWLINE* ENDMARKER
|
||||||
|
|
||||||
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
|
decorator: '@' namedexpr_test NEWLINE
|
||||||
decorators: decorator+
|
decorators: decorator+
|
||||||
decorated: decorators (classdef | funcdef | async_funcdef)
|
decorated: decorators (classdef | funcdef | async_funcdef)
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ PyAPI_FUNC(Py_ssize_t) _PyNode_SizeOf(node *n);
|
||||||
#define NCH(n) ((n)->n_nchildren)
|
#define NCH(n) ((n)->n_nchildren)
|
||||||
|
|
||||||
#define CHILD(n, i) (&(n)->n_child[i])
|
#define CHILD(n, i) (&(n)->n_child[i])
|
||||||
#define RCHILD(n, i) (CHILD(n, NCH(n) + i))
|
|
||||||
#define TYPE(n) ((n)->n_type)
|
#define TYPE(n) ((n)->n_type)
|
||||||
#define STR(n) ((n)->n_str)
|
#define STR(n) ((n)->n_str)
|
||||||
#define LINENO(n) ((n)->n_lineno)
|
#define LINENO(n) ((n)->n_lineno)
|
||||||
|
|
|
@ -151,21 +151,18 @@ class TestDecorators(unittest.TestCase):
|
||||||
self.assertEqual(counts['double'], 4)
|
self.assertEqual(counts['double'], 4)
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
# Test syntax restrictions - these are all compile-time errors:
|
|
||||||
#
|
|
||||||
for expr in [ "1+2", "x[3]", "(1, 2)" ]:
|
|
||||||
# Sanity check: is expr is a valid expression by itself?
|
|
||||||
compile(expr, "testexpr", "exec")
|
|
||||||
|
|
||||||
codestr = "@%s\ndef f(): pass" % expr
|
# Test SyntaxErrors:
|
||||||
self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
|
for stmt in ("x,", "x, y", "x = y", "pass", "import sys"):
|
||||||
|
compile(stmt, "test", "exec") # Sanity check.
|
||||||
|
with self.assertRaises(SyntaxError):
|
||||||
|
compile(f"@{stmt}\ndef f(): pass", "test", "exec")
|
||||||
|
|
||||||
# You can't put multiple decorators on a single line:
|
# Test TypeErrors that used to be SyntaxErrors:
|
||||||
#
|
for expr in ("1.+2j", "[1, 2][-1]", "(1, 2)", "True", "...", "None"):
|
||||||
self.assertRaises(SyntaxError, compile,
|
compile(expr, "test", "eval") # Sanity check.
|
||||||
"@f1 @f2\ndef f(): pass", "test", "exec")
|
with self.assertRaises(TypeError):
|
||||||
|
exec(f"@{expr}\ndef f(): pass")
|
||||||
# Test runtime errors
|
|
||||||
|
|
||||||
def unimp(func):
|
def unimp(func):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -179,6 +176,13 @@ class TestDecorators(unittest.TestCase):
|
||||||
code = compile(codestr, "test", "exec")
|
code = compile(codestr, "test", "exec")
|
||||||
self.assertRaises(exc, eval, code, context)
|
self.assertRaises(exc, eval, code, context)
|
||||||
|
|
||||||
|
def test_expressions(self):
|
||||||
|
for expr in (
|
||||||
|
"(x,)", "(x, y)", "x := y", "(x := y)", "x @y", "(x @ y)", "x[0]",
|
||||||
|
"w[x].y.z", "w + x - (y + z)", "x(y)()(z)", "[w, x, y][z]", "x.y",
|
||||||
|
):
|
||||||
|
compile(f"@{expr}\ndef f(): pass", "test", "exec")
|
||||||
|
|
||||||
def test_double(self):
|
def test_double(self):
|
||||||
class C(object):
|
class C(object):
|
||||||
@funcattrs(abc=1, xyz="haha")
|
@funcattrs(abc=1, xyz="haha")
|
||||||
|
|
|
@ -460,7 +460,7 @@ class GrammarTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_funcdef(self):
|
def test_funcdef(self):
|
||||||
### [decorators] 'def' NAME parameters ['->' test] ':' suite
|
### [decorators] 'def' NAME parameters ['->' test] ':' suite
|
||||||
### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
|
### decorator: '@' namedexpr_test NEWLINE
|
||||||
### decorators: decorator+
|
### decorators: decorator+
|
||||||
### parameters: '(' [typedargslist] ')'
|
### parameters: '(' [typedargslist] ')'
|
||||||
### typedargslist: ((tfpdef ['=' test] ',')*
|
### typedargslist: ((tfpdef ['=' test] ',')*
|
||||||
|
@ -666,6 +666,20 @@ class GrammarTests(unittest.TestCase):
|
||||||
def f(x) -> list: pass
|
def f(x) -> list: pass
|
||||||
self.assertEqual(f.__annotations__, {'return': list})
|
self.assertEqual(f.__annotations__, {'return': list})
|
||||||
|
|
||||||
|
# Test expressions as decorators (PEP 614):
|
||||||
|
@False or null
|
||||||
|
def f(x): pass
|
||||||
|
@d := null
|
||||||
|
def f(x): pass
|
||||||
|
@lambda f: null(f)
|
||||||
|
def f(x): pass
|
||||||
|
@[..., null, ...][1]
|
||||||
|
def f(x): pass
|
||||||
|
@null(null)(null)
|
||||||
|
def f(x): pass
|
||||||
|
@[null][0].__call__.__call__
|
||||||
|
def f(x): pass
|
||||||
|
|
||||||
# test closures with a variety of opargs
|
# test closures with a variety of opargs
|
||||||
closure = 1
|
closure = 1
|
||||||
def f(): return closure
|
def f(): return closure
|
||||||
|
@ -1515,13 +1529,27 @@ class GrammarTests(unittest.TestCase):
|
||||||
def meth2(self, arg): pass
|
def meth2(self, arg): pass
|
||||||
def meth3(self, a1, a2): pass
|
def meth3(self, a1, a2): pass
|
||||||
|
|
||||||
# decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
|
# decorator: '@' namedexpr_test NEWLINE
|
||||||
# decorators: decorator+
|
# decorators: decorator+
|
||||||
# decorated: decorators (classdef | funcdef)
|
# decorated: decorators (classdef | funcdef)
|
||||||
def class_decorator(x): return x
|
def class_decorator(x): return x
|
||||||
@class_decorator
|
@class_decorator
|
||||||
class G: pass
|
class G: pass
|
||||||
|
|
||||||
|
# Test expressions as decorators (PEP 614):
|
||||||
|
@False or class_decorator
|
||||||
|
class H: pass
|
||||||
|
@d := class_decorator
|
||||||
|
class I: pass
|
||||||
|
@lambda c: class_decorator(c)
|
||||||
|
class J: pass
|
||||||
|
@[..., class_decorator, ...][1]
|
||||||
|
class K: pass
|
||||||
|
@class_decorator(class_decorator)(class_decorator)
|
||||||
|
class L: pass
|
||||||
|
@[class_decorator][0].__call__.__call__
|
||||||
|
class M: pass
|
||||||
|
|
||||||
def test_dictcomps(self):
|
def test_dictcomps(self):
|
||||||
# dictorsetmaker: ( (test ':' test (comp_for |
|
# dictorsetmaker: ( (test ':' test (comp_for |
|
||||||
# (',' test ':' test)* [','])) |
|
# (',' test ':' test)* [','])) |
|
||||||
|
|
|
@ -226,6 +226,27 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase):
|
||||||
self.check_suite("@funcattrs()\n"
|
self.check_suite("@funcattrs()\n"
|
||||||
"def f(): pass")
|
"def f(): pass")
|
||||||
|
|
||||||
|
self.check_suite("@False or x\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@d := x\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@lambda f: x(f)\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@[..., x, ...][1]\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@x(x)(x)\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@(x, x)\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@...\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@None\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@w @(x @y) @(z)\n"
|
||||||
|
"def f(): pass")
|
||||||
|
self.check_suite("@w[x].y.z\n"
|
||||||
|
"def f(): pass")
|
||||||
|
|
||||||
# keyword-only arguments
|
# keyword-only arguments
|
||||||
self.check_suite("def f(*, a): pass")
|
self.check_suite("def f(*, a): pass")
|
||||||
self.check_suite("def f(*, a = 5): pass")
|
self.check_suite("def f(*, a = 5): pass")
|
||||||
|
@ -270,6 +291,27 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase):
|
||||||
"@decorator2\n"
|
"@decorator2\n"
|
||||||
"class foo():pass")
|
"class foo():pass")
|
||||||
|
|
||||||
|
self.check_suite("@False or x\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@d := x\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@lambda f: x(f)\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@[..., x, ...][1]\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@x(x)(x)\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@(x, x)\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@...\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@None\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@w @(x @y) @(z)\n"
|
||||||
|
"class C: pass")
|
||||||
|
self.check_suite("@w[x].y.z\n"
|
||||||
|
"class C: pass")
|
||||||
|
|
||||||
def test_import_from_statement(self):
|
def test_import_from_statement(self):
|
||||||
self.check_suite("from sys.path import *")
|
self.check_suite("from sys.path import *")
|
||||||
self.check_suite("from sys.path import dirname")
|
self.check_suite("from sys.path import dirname")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Relax :term:`decorator` grammar restrictions to allow any valid expression
|
||||||
|
(:pep:`614`).
|
70
Python/ast.c
70
Python/ast.c
|
@ -1691,80 +1691,16 @@ ast_for_arguments(struct compiling *c, const node *n)
|
||||||
return arguments(posonlyargs, posargs, vararg, kwonlyargs, kwdefaults, kwarg, posdefaults, c->c_arena);
|
return arguments(posonlyargs, posargs, vararg, kwonlyargs, kwdefaults, kwarg, posdefaults, c->c_arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
static expr_ty
|
|
||||||
ast_for_dotted_name(struct compiling *c, const node *n)
|
|
||||||
{
|
|
||||||
expr_ty e;
|
|
||||||
identifier id;
|
|
||||||
int lineno, col_offset;
|
|
||||||
int i;
|
|
||||||
node *ch;
|
|
||||||
|
|
||||||
REQ(n, dotted_name);
|
|
||||||
|
|
||||||
lineno = LINENO(n);
|
|
||||||
col_offset = n->n_col_offset;
|
|
||||||
|
|
||||||
ch = CHILD(n, 0);
|
|
||||||
id = NEW_IDENTIFIER(ch);
|
|
||||||
if (!id)
|
|
||||||
return NULL;
|
|
||||||
e = Name(id, Load, lineno, col_offset,
|
|
||||||
ch->n_end_lineno, ch->n_end_col_offset, c->c_arena);
|
|
||||||
if (!e)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
for (i = 2; i < NCH(n); i+=2) {
|
|
||||||
const node *child = CHILD(n, i);
|
|
||||||
id = NEW_IDENTIFIER(child);
|
|
||||||
if (!id)
|
|
||||||
return NULL;
|
|
||||||
e = Attribute(e, id, Load, lineno, col_offset,
|
|
||||||
child->n_end_lineno, child->n_end_col_offset, c->c_arena);
|
|
||||||
if (!e)
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
static expr_ty
|
static expr_ty
|
||||||
ast_for_decorator(struct compiling *c, const node *n)
|
ast_for_decorator(struct compiling *c, const node *n)
|
||||||
{
|
{
|
||||||
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE */
|
/* decorator: '@' namedexpr_test NEWLINE */
|
||||||
expr_ty d = NULL;
|
|
||||||
expr_ty name_expr;
|
|
||||||
|
|
||||||
REQ(n, decorator);
|
REQ(n, decorator);
|
||||||
REQ(CHILD(n, 0), AT);
|
REQ(CHILD(n, 0), AT);
|
||||||
REQ(RCHILD(n, -1), NEWLINE);
|
REQ(CHILD(n, 2), NEWLINE);
|
||||||
|
|
||||||
name_expr = ast_for_dotted_name(c, CHILD(n, 1));
|
return ast_for_expr(c, CHILD(n, 1));
|
||||||
if (!name_expr)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (NCH(n) == 3) { /* No arguments */
|
|
||||||
d = name_expr;
|
|
||||||
name_expr = NULL;
|
|
||||||
}
|
|
||||||
else if (NCH(n) == 5) { /* Call with no arguments */
|
|
||||||
d = Call(name_expr, NULL, NULL,
|
|
||||||
name_expr->lineno, name_expr->col_offset,
|
|
||||||
CHILD(n, 3)->n_end_lineno, CHILD(n, 3)->n_end_col_offset,
|
|
||||||
c->c_arena);
|
|
||||||
if (!d)
|
|
||||||
return NULL;
|
|
||||||
name_expr = NULL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
d = ast_for_call(c, CHILD(n, 3), name_expr,
|
|
||||||
CHILD(n, 1), CHILD(n, 2), CHILD(n, 4));
|
|
||||||
if (!d)
|
|
||||||
return NULL;
|
|
||||||
name_expr = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static asdl_seq*
|
static asdl_seq*
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue