From 0e2403f2f95a21912972422564de37cbe129c195 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 20 Feb 2019 18:08:23 -0800 Subject: [PATCH 01/14] Work towards feature_version support Visible behavior: - Issue error for `X @ Y` (matrix multiply) if c_feature_version < 5 - Add optional feature_version kw arg to ast.parse() (default -1 which implies PY_MINOR_VERSION) - Add feature_version: int = -1 to compile() (via Argument Clinic); this sets cf_feature_version to the given value if >= 0, else defaults to PY_MINOR_VERSION Implementation: - Add PyAST_obj2mod_ex(): like PyAST_obj2mod() but with feature_version arg; the latter calls the former with PY_MINOR_VERSION - Add cf_feature_version to PyCompilerFlags structure; initialized to PY_MINOR_VERSION everywhere - Add c_feature_version to struct compiling; initialize from cf_feature_version - Add 'c' argument to get_operator() - In builtin eval() and exec(), default to PY_MINOR_VERSION TODO: - Put version-dependent ASYNC/AWAIT keyword scanning back - Reject async functions, await expressions, and async for/with in minor versions < 5 - Reject async comprehensions in minor versions < 6 - Reject underscores in numeric literals in minor versions < 6 - Reject variable annotations in minor versions < 6 - Reject `X @= Y` in minor versions < 5 --- Include/Python-ast.h | 1 + Include/compile.h | 1 + Lib/ast.py | 9 +++++++-- Modules/main.c | 2 +- Modules/parsermodule.c | 5 ++++- Parser/asdl_c.py | 6 ++++++ Python/Python-ast.c | 5 +++++ Python/ast.c | 14 +++++++++++--- Python/bltinmodule.c | 11 +++++++++-- Python/clinic/bltinmodule.c.h | 15 ++++++++------- Python/compile.c | 1 + Python/pythonrun.c | 4 ++++ 12 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Include/Python-ast.h b/Include/Python-ast.h index 52ba12755ef977..8265bed8d6e95d 100644 --- a/Include/Python-ast.h +++ b/Include/Python-ast.h @@ -703,6 +703,7 @@ type_ignore_ty _Py_TypeIgnore(int lineno, PyArena *arena); PyObject* PyAST_mod2obj(mod_ty t); mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode); +mod_ty PyAST_obj2mod_ex(PyObject* ast, PyArena* arena, int mode, int feature_version); int PyAST_Check(PyObject* obj); #ifdef __cplusplus diff --git a/Include/compile.h b/Include/compile.h index d0bbed8f558b15..13708678675f7b 100644 --- a/Include/compile.h +++ b/Include/compile.h @@ -27,6 +27,7 @@ PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *); #ifndef Py_LIMITED_API typedef struct { int cf_flags; /* bitmask of CO_xxx flags relevant to future */ + int cf_feature_version; /* minor Python version (PyCF_ONLY_AST) */ } PyCompilerFlags; #endif diff --git a/Lib/ast.py b/Lib/ast.py index 470a74b3b5ff79..819be1f4c3cabe 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -27,7 +27,8 @@ from _ast import * -def parse(source, filename='', mode='exec', *, type_comments=False): +def parse(source, filename='', mode='exec', *, + type_comments=False, feature_version=-1): """ Parse the source into an AST node. Equivalent to compile(source, filename, mode, PyCF_ONLY_AST). @@ -36,7 +37,11 @@ def parse(source, filename='', mode='exec', *, type_comments=False): flags = PyCF_ONLY_AST if type_comments: flags |= PyCF_TYPE_COMMENTS - return compile(source, filename, mode, flags) + return compile(source, filename, mode, flags, + dont_inherit=False, + optimize=-1, + feature_version=feature_version, + ) def literal_eval(node_or_string): diff --git a/Modules/main.c b/Modules/main.c index 34032adca57eed..e30a8bd8d82cd6 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -821,7 +821,7 @@ pymain_run_python(PyInterpreterState *interp, int *exitcode) Py_DECREF(path0); } - PyCompilerFlags cf = {.cf_flags = 0}; + PyCompilerFlags cf = {.cf_flags = 0, .cf_feature_version = PY_MINOR_VERSION}; pymain_header(config); pymain_import_readline(config); diff --git a/Modules/parsermodule.c b/Modules/parsermodule.c index 87f58d340c2dbe..d4e2be643e7120 100644 --- a/Modules/parsermodule.c +++ b/Modules/parsermodule.c @@ -341,6 +341,7 @@ parser_newstobject(node *st, int type) o->st_node = st; o->st_type = type; o->st_flags.cf_flags = 0; + o->st_flags.cf_feature_version = PY_MINOR_VERSION; } else { PyNode_Free(st); @@ -584,8 +585,10 @@ parser_do_parse(PyObject *args, PyObject *kw, const char *argspec, int type) if (n) { res = parser_newstobject(n, type); - if (res) + if (res) { ((PyST_Object *)res)->st_flags.cf_flags = flags & PyCF_MASK; + ((PyST_Object *)res)->st_flags.cf_feature_version = PY_MINOR_VERSION; + } } else { PyParser_SetError(&err); diff --git a/Parser/asdl_c.py b/Parser/asdl_c.py index 1526995e3f8b55..52247559d1a937 100644 --- a/Parser/asdl_c.py +++ b/Parser/asdl_c.py @@ -1188,6 +1188,11 @@ class PartingShots(StaticVisitor): /* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */ mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode) +{ + return PyAST_obj2mod_ex(ast, arena, mode, PY_MINOR_VERSION); +} + +mod_ty PyAST_obj2mod_ex(PyObject* ast, PyArena* arena, int mode, int feature_version) { mod_ty res; PyObject *req_type[3]; @@ -1269,6 +1274,7 @@ def main(srcfile, dump_module=False): f.write("\n") f.write("PyObject* PyAST_mod2obj(mod_ty t);\n") f.write("mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode);\n") + f.write("mod_ty PyAST_obj2mod_ex(PyObject* ast, PyArena* arena, int mode, int feature_version);\n") f.write("int PyAST_Check(PyObject* obj);\n") f.write('\n') f.write('#ifdef __cplusplus\n') diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 92ec1575714485..0411f9f07f27af 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -8899,6 +8899,11 @@ PyObject* PyAST_mod2obj(mod_ty t) /* mode is 0 for "exec", 1 for "eval" and 2 for "single" input */ mod_ty PyAST_obj2mod(PyObject* ast, PyArena* arena, int mode) +{ + return PyAST_obj2mod_ex(ast, arena, mode, PY_MINOR_VERSION); +} + +mod_ty PyAST_obj2mod_ex(PyObject* ast, PyArena* arena, int mode, int feature_version) { mod_ty res; PyObject *req_type[3]; diff --git a/Python/ast.c b/Python/ast.c index 2e960485f47c09..276e81e558786e 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -564,6 +564,7 @@ struct compiling { PyArena *c_arena; /* Arena for allocating memory. */ PyObject *c_filename; /* filename */ PyObject *c_normalize; /* Normalization function from unicodedata. */ + int c_feature_version; /* Latest minor version of Python for allowed features */ }; static asdl_seq *seq_for_testlist(struct compiling *, const node *); @@ -783,6 +784,7 @@ PyAST_FromNodeObject(const node *n, PyCompilerFlags *flags, /* borrowed reference */ c.c_filename = filename; c.c_normalize = NULL; + c.c_feature_version = flags->cf_feature_version; if (TYPE(n) == encoding_decl) n = CHILD(n, 0); @@ -955,7 +957,7 @@ PyAST_FromNode(const node *n, PyCompilerFlags *flags, const char *filename_str, */ static operator_ty -get_operator(const node *n) +get_operator(struct compiling *c, const node *n) { switch (TYPE(n)) { case VBAR: @@ -975,6 +977,11 @@ get_operator(const node *n) case STAR: return Mult; case AT: + if (c->c_feature_version < 5) { + ast_error(c, n, + "The '@' operator is only supported in Python 3.5 and greater"); + return (operator_ty)0; + } return MatMult; case SLASH: return Div; @@ -2524,7 +2531,7 @@ ast_for_binop(struct compiling *c, const node *n) if (!expr2) return NULL; - newoperator = get_operator(CHILD(n, 1)); + newoperator = get_operator(c, CHILD(n, 1)); if (!newoperator) return NULL; @@ -2539,7 +2546,7 @@ ast_for_binop(struct compiling *c, const node *n) expr_ty tmp_result, tmp; const node* next_oper = CHILD(n, i * 2 + 1); - newoperator = get_operator(next_oper); + newoperator = get_operator(c, next_oper); if (!newoperator) return NULL; @@ -4768,6 +4775,7 @@ fstring_compile_expr(const char *expr_start, const char *expr_end, str[len+2] = 0; cf.cf_flags = PyCF_ONLY_AST; + cf.cf_feature_version = PY_MINOR_VERSION; mod_n = PyParser_SimpleParseStringFlagsFilename(str, "", Py_eval_input, 0); if (!mod_n) { diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index a19b8b8ddc86ce..7a2b259cbd89c8 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -745,6 +745,7 @@ compile as builtin_compile flags: int = 0 dont_inherit: bool(accept={int}) = False optimize: int = -1 + feature_version: int = -1 Compile source into a code object that can be executed by exec() or eval(). @@ -763,8 +764,8 @@ in addition to any features explicitly specified. static PyObject * builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename, const char *mode, int flags, int dont_inherit, - int optimize) -/*[clinic end generated code: output=1fa176e33452bb63 input=0ff726f595eb9fcd]*/ + int optimize, int feature_version) +/*[clinic end generated code: output=b0c09c84f116d3d7 input=5fcc30651a6acaa9]*/ { PyObject *source_copy; const char *str; @@ -775,6 +776,10 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename, PyObject *result; cf.cf_flags = flags | PyCF_SOURCE_IS_UTF8; + cf.cf_feature_version = PY_MINOR_VERSION; + if (feature_version >= 0 && (flags & PyCF_ONLY_AST)) { + cf.cf_feature_version = feature_version; + } if (flags & ~(PyCF_MASK | PyCF_MASK_OBSOLETE | PyCF_DONT_IMPLY_DEDENT | PyCF_ONLY_AST | PyCF_TYPE_COMMENTS)) @@ -981,6 +986,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals, } cf.cf_flags = PyCF_SOURCE_IS_UTF8; + cf.cf_feature_version = PY_MINOR_VERSION; str = source_as_string(source, "eval", "string, bytes or code", &cf, &source_copy); if (str == NULL) return NULL; @@ -1068,6 +1074,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals, const char *str; PyCompilerFlags cf; cf.cf_flags = PyCF_SOURCE_IS_UTF8; + cf.cf_feature_version = PY_MINOR_VERSION; str = source_as_string(source, "exec", "string, bytes or code", &cf, &source_copy); diff --git a/Python/clinic/bltinmodule.c.h b/Python/clinic/bltinmodule.c.h index 1b82f773edd8a9..df9970942b7423 100644 --- a/Python/clinic/bltinmodule.c.h +++ b/Python/clinic/bltinmodule.c.h @@ -151,7 +151,7 @@ builtin_chr(PyObject *module, PyObject *arg) PyDoc_STRVAR(builtin_compile__doc__, "compile($module, /, source, filename, mode, flags=0,\n" -" dont_inherit=False, optimize=-1)\n" +" dont_inherit=False, optimize=-1, feature_version=-1)\n" "--\n" "\n" "Compile source into a code object that can be executed by exec() or eval().\n" @@ -173,26 +173,27 @@ PyDoc_STRVAR(builtin_compile__doc__, static PyObject * builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename, const char *mode, int flags, int dont_inherit, - int optimize); + int optimize, int feature_version); static PyObject * builtin_compile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; - static const char * const _keywords[] = {"source", "filename", "mode", "flags", "dont_inherit", "optimize", NULL}; - static _PyArg_Parser _parser = {"OO&s|iii:compile", _keywords, 0}; + static const char * const _keywords[] = {"source", "filename", "mode", "flags", "dont_inherit", "optimize", "feature_version", NULL}; + static _PyArg_Parser _parser = {"OO&s|iiii:compile", _keywords, 0}; PyObject *source; PyObject *filename; const char *mode; int flags = 0; int dont_inherit = 0; int optimize = -1; + int feature_version = -1; if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &source, PyUnicode_FSDecoder, &filename, &mode, &flags, &dont_inherit, &optimize)) { + &source, PyUnicode_FSDecoder, &filename, &mode, &flags, &dont_inherit, &optimize, &feature_version)) { goto exit; } - return_value = builtin_compile_impl(module, source, filename, mode, flags, dont_inherit, optimize); + return_value = builtin_compile_impl(module, source, filename, mode, flags, dont_inherit, optimize, feature_version); exit: return return_value; @@ -754,4 +755,4 @@ builtin_issubclass(PyObject *module, PyObject *const *args, Py_ssize_t nargs) exit: return return_value; } -/*[clinic end generated code: output=54e5e33dcc2659e0 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=00b97a48ea49eaf2 input=a9049054013a1b77]*/ diff --git a/Python/compile.c b/Python/compile.c index c26210675d8713..697833752bb0bd 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -330,6 +330,7 @@ PyAST_CompileObject(mod_ty mod, PyObject *filename, PyCompilerFlags *flags, goto finally; if (!flags) { local_flags.cf_flags = 0; + local_flags.cf_feature_version = PY_MINOR_VERSION; flags = &local_flags; } merged = c.c_future->ff_features | flags->cf_flags; diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 906877a0a853ca..ae6778d8a58638 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -105,6 +105,7 @@ PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags * if (flags == NULL) { flags = &local_flags; local_flags.cf_flags = 0; + local_flags.cf_feature_version = PY_MINOR_VERSION; } v = _PySys_GetObjectId(&PyId_ps1); if (v == NULL) { @@ -1165,6 +1166,7 @@ Py_SymtableStringObject(const char *str, PyObject *filename, int start) return NULL; flags.cf_flags = 0; + flags.cf_feature_version = PY_MINOR_VERSION; mod = PyParser_ASTFromStringObject(str, filename, start, &flags, arena); if (mod == NULL) { PyArena_Free(arena); @@ -1204,6 +1206,7 @@ PyParser_ASTFromStringObject(const char *s, PyObject *filename, int start, &iflags); if (flags == NULL) { localflags.cf_flags = 0; + localflags.cf_feature_version = PY_MINOR_VERSION; flags = &localflags; } if (n) { @@ -1249,6 +1252,7 @@ PyParser_ASTFromFileObject(FILE *fp, PyObject *filename, const char* enc, start, ps1, ps2, &err, &iflags); if (flags == NULL) { localflags.cf_flags = 0; + localflags.cf_feature_version = PY_MINOR_VERSION; flags = &localflags; } if (n) { From 8caf275763a2c232d81ba0ac1900955215c99bd3 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 11:58:04 -0800 Subject: [PATCH 02/14] Implement further restrictions on feature_version This is everything currently in typeshed except await expressions (but it does reject async functions etc.): - Reject async functions and async for/with in minor versions < 5 - Reject async comprehensions in minor versions < 6 - Reject underscores in numeric literals in minor versions < 6 - Reject variable annotations in minor versions < 6 - Reject `X @= Y` in minor versions < 5 --- Python/ast.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/Python/ast.c b/Python/ast.c index 276e81e558786e..f1b3dccfbeed01 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -1216,6 +1216,11 @@ ast_for_augassign(struct compiling *c, const node *n) else return Mult; case '@': + if (c->c_feature_version < 5) { + ast_error(c, n, + "The '@' operator is only supported in Python 3.5 and greater"); + return (operator_ty)0; + } return MatMult; default: PyErr_Format(PyExc_SystemError, "invalid augassign: %s", STR(n)); @@ -1726,6 +1731,12 @@ ast_for_funcdef_impl(struct compiling *c, const node *n0, node *tc; string type_comment = NULL; + if (is_async && c->c_feature_version < 5) { + ast_error(c, n, + "Async functions are only supported in Python 3.5 and greater"); + return NULL; + } + REQ(n, funcdef); name = NEW_IDENTIFIER(CHILD(n, name_i)); @@ -2050,6 +2061,13 @@ ast_for_comprehension(struct compiling *c, const node *n) } REQ(sync_n, sync_comp_for); + /* Async comprehensions only allowed in Python 3.6 and greater */ + if (is_async && c->c_feature_version < 6) { + ast_error(c, n, + "Async comprehensions are only supported in Python 3.6 and greater"); + return NULL; + } + for_ch = CHILD(sync_n, 1); t = ast_for_exprlist(c, for_ch, Store); if (!t) @@ -2344,7 +2362,15 @@ ast_for_atom(struct compiling *c, const node *n) return str; } case NUMBER: { - PyObject *pynum = parsenumber(c, STR(ch)); + PyObject *pynum; + /* Underscores in numeric literals are only allowed in Python 3.6 or greater */ + /* Check for underscores here rather than in parse_number so we can report a line number on error */ + if (c->c_feature_version < 6 && strchr(STR(ch), '_') != NULL) { + ast_error(c, ch, + "Underscores in numeric literals are only supported in Python 3.6 and greater"); + return NULL; + } + pynum = parsenumber(c, STR(ch)); if (!pynum) return NULL; @@ -3240,6 +3266,13 @@ ast_for_expr_stmt(struct compiling *c, const node *n) node *deep, *ann = CHILD(n, 1); int simple = 1; + /* AnnAssigns are only allowed in Python 3.6 or greater */ + if (c->c_feature_version < 6) { + ast_error(c, ch, + "Variable annotation syntax is only supported in Python 3.6 and greater"); + return NULL; + } + /* we keep track of parens to qualify (x) as expression not name */ deep = ch; while (NCH(deep) == 1) { @@ -4057,6 +4090,13 @@ ast_for_for_stmt(struct compiling *c, const node *n0, bool is_async) int end_lineno, end_col_offset; int has_type_comment; string type_comment; + + if (is_async && c->c_feature_version < 5) { + ast_error(c, n, + "Async for loops are only supported in Python 3.5 and greater"); + return NULL; + } + /* for_stmt: 'for' exprlist 'in' testlist ':' [TYPE_COMMENT] suite ['else' ':' suite] */ REQ(n, for_stmt); @@ -4285,6 +4325,12 @@ ast_for_with_stmt(struct compiling *c, const node *n0, bool is_async) asdl_seq *items, *body; string type_comment; + if (is_async && c->c_feature_version < 5) { + ast_error(c, n, + "Async with statements are only supported in Python 3.5 and greater"); + return NULL; + } + REQ(n, with_stmt); has_type_comment = TYPE(CHILD(n, NCH(n) - 2)) == TYPE_COMMENT; @@ -5576,6 +5622,13 @@ parsestr(struct compiling *c, const node *n, int *bytesmode, int *rawmode, } } } + + /* fstrings are only allowed in Python 3.6 and greater */ + if (fmode && c->c_feature_version < 6) { + ast_error(c, n, "Format strings are only supported in Python 3.6 and greater"); + return -1; + } + if (fmode && *bytesmode) { PyErr_BadInternalCall(); return -1; From 713d05089de6554928306378ac95a506472a4a51 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 15:14:06 -0800 Subject: [PATCH 03/14] Complete feature_version implementation (except loose ends) This adds: - Add ASYNC/AWAIT tokens back to Grammar and regenerate - Recognize async/await keywords conditionally if feature_version < 7 - Reject await expressions if feature_version < 5 - Docs for ASYNC/AWAIT tokens and for ast.parse(..., feature_version=N) --- Doc/library/ast.rst | 9 +- Doc/library/token-list.inc | 4 + Doc/library/token.rst | 3 + Grammar/Grammar | 8 +- Grammar/Tokens | 2 + Include/parsetok.h | 1 + Include/token.h | 10 +- Lib/token.py | 16 +-- Parser/parsetok.c | 2 + Parser/token.c | 2 + Parser/tokenizer.c | 79 ++++++++++++++ Parser/tokenizer.h | 7 ++ Python/ast.c | 25 ++--- Python/graminit.c | 210 ++++++++++++++++++------------------- Python/pythonrun.c | 2 + 15 files changed, 246 insertions(+), 134 deletions(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 3df7f9ebc70cc7..742238611628ce 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -126,7 +126,7 @@ The abstract grammar is currently defined as follows: Apart from the node classes, the :mod:`ast` module defines these utility functions and classes for traversing abstract syntax trees: -.. function:: parse(source, filename='', mode='exec', *, type_comments=False) +.. function:: parse(source, filename='', mode='exec', *, type_comments=False, feature_version=-1) Parse the source into an AST node. Equivalent to ``compile(source, filename, mode, ast.PyCF_ONLY_AST)``. @@ -145,13 +145,18 @@ and classes for traversing abstract syntax trees: modified to correspond to :pep:`484` "signature type comments", e.g. ``(str, int) -> List[str]``. + Also, setting ``feature_version`` to the minor version of an + earlier Python 3 version will attempt to parse using that version's + grammar. For example, setting ``feature_version=4`` will allow + the use of ``async`` and ``await`` as variable names. + .. warning:: It is possible to crash the Python interpreter with a sufficiently large/complex string due to stack depth limitations in Python's AST compiler. .. versionchanged:: 3.8 - Added ``type_comments=True`` and ``mode='func_type'``. + Added ``type_comments``, ``mode='func_type'`` and ``feature_version``. .. function:: literal_eval(node_or_string) diff --git a/Doc/library/token-list.inc b/Doc/library/token-list.inc index cb9fcd79effc98..877d39a432ed70 100644 --- a/Doc/library/token-list.inc +++ b/Doc/library/token-list.inc @@ -203,6 +203,10 @@ .. data:: OP +.. data:: AWAIT + +.. data:: ASYNC + .. data:: TYPE_IGNORE .. data:: TYPE_COMMENT diff --git a/Doc/library/token.rst b/Doc/library/token.rst index 4936e9aa08f43d..5c641ef46d145e 100644 --- a/Doc/library/token.rst +++ b/Doc/library/token.rst @@ -88,3 +88,6 @@ the :mod:`tokenize` module. .. versionchanged:: 3.8 Added :data:`TYPE_COMMENT`. + Added :data:`AWAIT` and :data:`ASYNC` tokens back (they're needed + to support parsing older Python versions for :func:`ast.parse` with + ``feature_version`` set to 6 or lower). diff --git a/Grammar/Grammar b/Grammar/Grammar index a42597805979e9..eaebdc4340f41c 100644 --- a/Grammar/Grammar +++ b/Grammar/Grammar @@ -18,7 +18,7 @@ decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE decorators: decorator+ decorated: decorators (classdef | funcdef | async_funcdef) -async_funcdef: 'async' funcdef +async_funcdef: ASYNC funcdef funcdef: 'def' NAME parameters ['->' test] ':' [TYPE_COMMENT] func_body_suite parameters: '(' [typedargslist] ')' @@ -70,7 +70,7 @@ nonlocal_stmt: 'nonlocal' NAME (',' NAME)* assert_stmt: 'assert' test [',' test] compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt -async_stmt: 'async' (funcdef | with_stmt | for_stmt) +async_stmt: ASYNC (funcdef | with_stmt | for_stmt) if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite] while_stmt: 'while' namedexpr_test ':' suite ['else' ':' suite] for_stmt: 'for' exprlist 'in' testlist ':' [TYPE_COMMENT] suite ['else' ':' suite] @@ -106,7 +106,7 @@ arith_expr: term (('+'|'-') term)* term: factor (('*'|'@'|'/'|'%'|'//') factor)* factor: ('+'|'-'|'~') factor | power power: atom_expr ['**' factor] -atom_expr: ['await'] atom trailer* +atom_expr: [AWAIT] atom trailer* atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | @@ -144,7 +144,7 @@ argument: ( test [comp_for] | comp_iter: comp_for | comp_if sync_comp_for: 'for' exprlist 'in' or_test [comp_iter] -comp_for: ['async'] sync_comp_for +comp_for: [ASYNC] sync_comp_for comp_if: 'if' test_nocond [comp_iter] # not used in grammar, but may appear in "node" passed from Parser to Compiler diff --git a/Grammar/Tokens b/Grammar/Tokens index 1d45e05ea21ddb..9de2da5d15fc3b 100644 --- a/Grammar/Tokens +++ b/Grammar/Tokens @@ -55,6 +55,8 @@ ELLIPSIS '...' COLONEQUAL ':=' OP +AWAIT +ASYNC TYPE_IGNORE TYPE_COMMENT ERRORTOKEN diff --git a/Include/parsetok.h b/Include/parsetok.h index be49f0005ac6c1..935d733e90a5af 100644 --- a/Include/parsetok.h +++ b/Include/parsetok.h @@ -35,6 +35,7 @@ typedef struct { #define PyPARSE_IGNORE_COOKIE 0x0010 #define PyPARSE_BARRY_AS_BDFL 0x0020 #define PyPARSE_TYPE_COMMENTS 0x0040 +#define PyPARSE_ASYNC_HACKS 0x0080 PyAPI_FUNC(node *) PyParser_ParseString(const char *, grammar *, int, perrdetail *); diff --git a/Include/token.h b/Include/token.h index ef6bf969b7c986..e08708baf196e8 100644 --- a/Include/token.h +++ b/Include/token.h @@ -65,10 +65,12 @@ extern "C" { #define ELLIPSIS 52 #define COLONEQUAL 53 #define OP 54 -#define TYPE_IGNORE 55 -#define TYPE_COMMENT 56 -#define ERRORTOKEN 57 -#define N_TOKENS 61 +#define AWAIT 55 +#define ASYNC 56 +#define TYPE_IGNORE 57 +#define TYPE_COMMENT 58 +#define ERRORTOKEN 59 +#define N_TOKENS 63 #define NT_OFFSET 256 /* Special definitions for cooperation with parser */ diff --git a/Lib/token.py b/Lib/token.py index 9bf80a5950a2b8..493bf0426508fe 100644 --- a/Lib/token.py +++ b/Lib/token.py @@ -58,14 +58,16 @@ ELLIPSIS = 52 COLONEQUAL = 53 OP = 54 -TYPE_IGNORE = 55 -TYPE_COMMENT = 56 +AWAIT = 55 +ASYNC = 56 +TYPE_IGNORE = 57 +TYPE_COMMENT = 58 # These aren't used by the C tokenizer but are needed for tokenize.py -ERRORTOKEN = 57 -COMMENT = 58 -NL = 59 -ENCODING = 60 -N_TOKENS = 61 +ERRORTOKEN = 59 +COMMENT = 60 +NL = 61 +ENCODING = 62 +N_TOKENS = 63 # Special definitions for cooperation with parser NT_OFFSET = 256 diff --git a/Parser/parsetok.c b/Parser/parsetok.c index 7a6c8865194335..ba33a9a0586f1a 100644 --- a/Parser/parsetok.c +++ b/Parser/parsetok.c @@ -101,6 +101,8 @@ PyParser_ParseStringObject(const char *s, PyObject *filename, Py_INCREF(err_ret->filename); tok->filename = err_ret->filename; + if (*flags & PyPARSE_ASYNC_HACKS) + tok->async_hacks = 1; return parsetok(tok, g, start, err_ret, flags); } diff --git a/Parser/token.c b/Parser/token.c index 228ecffc8415ad..a489668900901d 100644 --- a/Parser/token.c +++ b/Parser/token.c @@ -61,6 +61,8 @@ const char * const _PyParser_TokenNames[] = { "ELLIPSIS", "COLONEQUAL", "OP", + "AWAIT", + "ASYNC", "TYPE_IGNORE", "TYPE_COMMENT", "", diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c index 44ec41512cb1e6..8f0a9c810053a9 100644 --- a/Parser/tokenizer.c +++ b/Parser/tokenizer.c @@ -84,6 +84,11 @@ tok_new(void) tok->decoding_buffer = NULL; tok->type_comments = 0; + tok->async_hacks = 0; + tok->async_def = 0; + tok->async_def_indent = 0; + tok->async_def_nl = 0; + return tok; } @@ -1196,6 +1201,31 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end) } } + /* Peek ahead at the next character */ + c = tok_nextc(tok); + tok_backup(tok, c); + /* Check if we are closing an async function */ + if (tok->async_def + && !blankline + /* Due to some implementation artifacts of type comments, + * a TYPE_COMMENT at the start of a function won't set an + * indentation level and it will produce a NEWLINE after it. + * To avoid spuriously ending an async function due to this, + * wait until we have some non-newline char in front of us. */ + && c != '\n' + && tok->level == 0 + /* There was a NEWLINE after ASYNC DEF, + so we're past the signature. */ + && tok->async_def_nl + /* Current indentation level is less than where + the async function was defined */ + && tok->async_def_indent >= tok->indent) + { + tok->async_def = 0; + tok->async_def_indent = 0; + tok->async_def_nl = 0; + } + again: tok->start = NULL; /* Skip spaces */ @@ -1310,6 +1340,50 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end) *p_start = tok->start; *p_end = tok->cur; + /* async/await parsing block. */ + if (tok->cur - tok->start == 5 && tok->start[0] == 'a') { + /* May be an 'async' or 'await' token. For Python 3.7 or + later we recognize them unconditionally. For Python + 3.5 or 3.6 we recognize 'async' in front of 'def', and + either one inside of 'async def'. (Technically we + shouldn't recognize these at all for 3.4 or earlier, + but there's no *valid* Python 3.4 code that would be + rejected, and async functions will be rejected in a + later phase.) */ + if (!tok->async_hacks || tok->async_def) { + /* Always recognize the keywords. */ + if (memcmp(tok->start, "async", 5) == 0) { + return ASYNC; + } + if (memcmp(tok->start, "await", 5) == 0) { + return AWAIT; + } + } + else if (memcmp(tok->start, "async", 5) == 0) { + /* The current token is 'async'. + Look ahead one token to see if that is 'def'. */ + + struct tok_state ahead_tok; + char *ahead_tok_start = NULL, *ahead_tok_end = NULL; + int ahead_tok_kind; + + memcpy(&ahead_tok, tok, sizeof(ahead_tok)); + ahead_tok_kind = tok_get(&ahead_tok, &ahead_tok_start, + &ahead_tok_end); + + if (ahead_tok_kind == NAME + && ahead_tok.cur - ahead_tok.start == 3 + && memcmp(ahead_tok.start, "def", 3) == 0) + { + /* The next token is going to be 'def', so instead of + returning a plain NAME token, return ASYNC. */ + tok->async_def_indent = tok->indent; + tok->async_def = 1; + return ASYNC; + } + } + } + return NAME; } @@ -1322,6 +1396,11 @@ tok_get(struct tok_state *tok, char **p_start, char **p_end) *p_start = tok->start; *p_end = tok->cur - 1; /* Leave '\n' out of the string */ tok->cont_line = 0; + if (tok->async_def) { + /* We're somewhere inside an 'async def' function, and + we've encountered a NEWLINE after its signature. */ + tok->async_def_nl = 1; + } return NEWLINE; } diff --git a/Parser/tokenizer.h b/Parser/tokenizer.h index 22e91f7dca7624..06c7a14b70b061 100644 --- a/Parser/tokenizer.h +++ b/Parser/tokenizer.h @@ -64,6 +64,13 @@ struct tok_state { const char* input; /* Tokenizer's newline translated copy of the string. */ int type_comments; /* Whether to look for type comments */ + + /* async/await related fields (still needed depending on feature_version) */ + int async_hacks; /* =1 if async/await aren't always keywords */ + int async_def; /* =1 if tokens are inside an 'async def' body. */ + int async_def_indent; /* Indentation level of the outermost 'async def'. */ + int async_def_nl; /* =1 if the outermost 'async def' had at least one + NEWLINE token after it. */ }; extern struct tok_state *PyTokenizer_FromString(const char *, int); diff --git a/Python/ast.c b/Python/ast.c index f1b3dccfbeed01..2741926d67a78b 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -1790,10 +1790,9 @@ ast_for_funcdef_impl(struct compiling *c, const node *n0, static stmt_ty ast_for_async_funcdef(struct compiling *c, const node *n, asdl_seq *decorator_seq) { - /* async_funcdef: 'async' funcdef */ + /* async_funcdef: ASYNC funcdef */ REQ(n, async_funcdef); - REQ(CHILD(n, 0), NAME); - assert(strcmp(STR(CHILD(n, 0)), "async") == 0); + REQ(CHILD(n, 0), ASYNC); REQ(CHILD(n, 1), funcdef); return ast_for_funcdef_impl(c, n, decorator_seq, @@ -1812,10 +1811,9 @@ ast_for_funcdef(struct compiling *c, const node *n, asdl_seq *decorator_seq) static stmt_ty ast_for_async_stmt(struct compiling *c, const node *n) { - /* async_stmt: 'async' (funcdef | with_stmt | for_stmt) */ + /* async_stmt: ASYNC (funcdef | with_stmt | for_stmt) */ REQ(n, async_stmt); - REQ(CHILD(n, 0), NAME); - assert(strcmp(STR(CHILD(n, 0)), "async") == 0); + REQ(CHILD(n, 0), ASYNC); switch (TYPE(CHILD(n, 1))) { case funcdef: @@ -1966,8 +1964,7 @@ count_comp_fors(struct compiling *c, const node *n) n_fors++; REQ(n, comp_for); if (NCH(n) == 2) { - REQ(CHILD(n, 0), NAME); - assert(strcmp(STR(CHILD(n, 0)), "async") == 0); + REQ(CHILD(n, 0), ASYNC); n = CHILD(n, 1); } else if (NCH(n) == 1) { @@ -2052,8 +2049,7 @@ ast_for_comprehension(struct compiling *c, const node *n) if (NCH(n) == 2) { is_async = 1; - REQ(CHILD(n, 0), NAME); - assert(strcmp(STR(CHILD(n, 0)), "async") == 0); + REQ(CHILD(n, 0), ASYNC); sync_n = CHILD(n, 1); } else { @@ -2711,7 +2707,12 @@ ast_for_atom_expr(struct compiling *c, const node *n) REQ(n, atom_expr); nch = NCH(n); - if (TYPE(CHILD(n, 0)) == NAME && strcmp(STR(CHILD(n, 0)), "await") == 0) { + if (TYPE(CHILD(n, 0)) == AWAIT) { + if (c->c_feature_version < 5) { + ast_error(c, n, + "Await expressions are only supported in Python 3.5 and greater"); + return NULL; + } start = 1; assert(nch > 1); } @@ -2808,7 +2809,7 @@ ast_for_expr(struct compiling *c, const node *n) term: factor (('*'|'@'|'/'|'%'|'//') factor)* factor: ('+'|'-'|'~') factor | power power: atom_expr ['**' factor] - atom_expr: ['await'] atom trailer* + atom_expr: [AWAIT] atom trailer* yield_expr: 'yield' [yield_arg] */ diff --git a/Python/graminit.c b/Python/graminit.c index 6cb7bbc9f76ff3..24f6f6c72a6b07 100644 --- a/Python/graminit.c +++ b/Python/graminit.c @@ -106,7 +106,7 @@ static state states_5[3] = { {1, arcs_5_2}, }; static arc arcs_6_0[1] = { - {16, 1}, + {38, 1}, }; static arc arcs_6_1[1] = { {56, 2}, @@ -120,7 +120,7 @@ static state states_6[3] = { {1, arcs_6_2}, }; static arc arcs_7_0[1] = { - {21, 1}, + {19, 1}, }; static arc arcs_7_1[1] = { {40, 2}, @@ -583,7 +583,7 @@ static state states_19[2] = { {1, arcs_19_1}, }; static arc arcs_20_0[1] = { - {22, 1}, + {20, 1}, }; static arc arcs_20_1[1] = { {98, 2}, @@ -597,7 +597,7 @@ static state states_20[3] = { {1, arcs_20_2}, }; static arc arcs_21_0[1] = { - {31, 1}, + {29, 1}, }; static arc arcs_21_1[1] = { {0, 1}, @@ -621,7 +621,7 @@ static state states_22[2] = { {1, arcs_22_1}, }; static arc arcs_23_0[1] = { - {18, 1}, + {16, 1}, }; static arc arcs_23_1[1] = { {0, 1}, @@ -631,7 +631,7 @@ static state states_23[2] = { {1, arcs_23_1}, }; static arc arcs_24_0[1] = { - {20, 1}, + {18, 1}, }; static arc arcs_24_1[1] = { {0, 1}, @@ -641,7 +641,7 @@ static state states_24[2] = { {1, arcs_24_1}, }; static arc arcs_25_0[1] = { - {33, 1}, + {31, 1}, }; static arc arcs_25_1[2] = { {80, 2}, @@ -666,14 +666,14 @@ static state states_26[2] = { {1, arcs_26_1}, }; static arc arcs_27_0[1] = { - {32, 1}, + {30, 1}, }; static arc arcs_27_1[2] = { {60, 2}, {0, 1}, }; static arc arcs_27_2[2] = { - {24, 3}, + {22, 3}, {0, 2}, }; static arc arcs_27_3[1] = { @@ -701,7 +701,7 @@ static state states_28[2] = { {1, arcs_28_1}, }; static arc arcs_29_0[1] = { - {27, 1}, + {25, 1}, }; static arc arcs_29_1[1] = { {106, 2}, @@ -715,7 +715,7 @@ static state states_29[3] = { {1, arcs_29_2}, }; static arc arcs_30_0[1] = { - {24, 1}, + {22, 1}, }; static arc arcs_30_1[3] = { {107, 2}, @@ -725,11 +725,11 @@ static arc arcs_30_1[3] = { static arc arcs_30_2[4] = { {107, 2}, {9, 2}, - {27, 4}, + {25, 4}, {49, 3}, }; static arc arcs_30_3[1] = { - {27, 4}, + {25, 4}, }; static arc arcs_30_4[3] = { {5, 5}, @@ -832,7 +832,7 @@ static state states_35[2] = { {2, arcs_35_1}, }; static arc arcs_36_0[1] = { - {25, 1}, + {23, 1}, }; static arc arcs_36_1[1] = { {40, 2}, @@ -847,7 +847,7 @@ static state states_36[3] = { {2, arcs_36_2}, }; static arc arcs_37_0[1] = { - {29, 1}, + {27, 1}, }; static arc arcs_37_1[1] = { {40, 2}, @@ -903,7 +903,7 @@ static state states_39[2] = { {1, arcs_39_1}, }; static arc arcs_40_0[1] = { - {16, 1}, + {38, 1}, }; static arc arcs_40_1[3] = { {113, 2}, @@ -919,7 +919,7 @@ static state states_40[3] = { {1, arcs_40_2}, }; static arc arcs_41_0[1] = { - {26, 1}, + {24, 1}, }; static arc arcs_41_1[1] = { {118, 2}, @@ -955,7 +955,7 @@ static state states_41[8] = { {1, arcs_41_7}, }; static arc arcs_42_0[1] = { - {35, 1}, + {33, 1}, }; static arc arcs_42_1[1] = { {118, 2}, @@ -990,7 +990,7 @@ static state states_42[8] = { {1, arcs_42_7}, }; static arc arcs_43_0[1] = { - {23, 1}, + {21, 1}, }; static arc arcs_43_1[1] = { {98, 2}, @@ -1038,7 +1038,7 @@ static state states_43[11] = { {1, arcs_43_10}, }; static arc arcs_44_0[1] = { - {34, 1}, + {32, 1}, }; static arc arcs_44_1[1] = { {59, 2}, @@ -1097,7 +1097,7 @@ static state states_44[13] = { {2, arcs_44_12}, }; static arc arcs_45_0[1] = { - {36, 1}, + {34, 1}, }; static arc arcs_45_1[1] = { {125, 2}, @@ -1218,7 +1218,7 @@ static arc arcs_50_1[1] = { {0, 1}, }; static arc arcs_50_2[2] = { - {26, 3}, + {24, 3}, {0, 2}, }; static arc arcs_50_3[1] = { @@ -1250,7 +1250,7 @@ static state states_51[2] = { {1, arcs_51_1}, }; static arc arcs_52_0[1] = { - {28, 1}, + {26, 1}, }; static arc arcs_52_1[2] = { {59, 2}, @@ -1273,7 +1273,7 @@ static state states_52[5] = { {1, arcs_52_4}, }; static arc arcs_53_0[1] = { - {28, 1}, + {26, 1}, }; static arc arcs_53_1[2] = { {59, 2}, @@ -1318,7 +1318,7 @@ static state states_55[2] = { {2, arcs_55_1}, }; static arc arcs_56_0[2] = { - {30, 1}, + {28, 1}, {139, 2}, }; static arc arcs_56_1[1] = { @@ -1353,13 +1353,13 @@ static arc arcs_58_0[10] = { {146, 1}, {122, 1}, {147, 2}, - {30, 3}, + {28, 3}, }; static arc arcs_58_1[1] = { {0, 1}, }; static arc arcs_58_2[2] = { - {30, 1}, + {28, 1}, {0, 2}, }; static arc arcs_58_3[1] = { @@ -1460,7 +1460,7 @@ static state states_65[2] = { static arc arcs_66_0[4] = { {7, 1}, {8, 1}, - {39, 1}, + {37, 1}, {162, 2}, }; static arc arcs_66_1[1] = { @@ -1494,7 +1494,7 @@ static state states_67[4] = { {1, arcs_67_3}, }; static arc arcs_68_0[2] = { - {17, 1}, + {39, 1}, {164, 2}, }; static arc arcs_68_1[1] = { @@ -1516,7 +1516,7 @@ static arc arcs_69_0[10] = { {12, 2}, {13, 2}, {14, 3}, - {38, 4}, + {36, 4}, {40, 2}, {41, 2}, {42, 5}, @@ -1788,7 +1788,7 @@ static state states_77[14] = { {1, arcs_77_13}, }; static arc arcs_78_0[1] = { - {19, 1}, + {17, 1}, }; static arc arcs_78_1[1] = { {40, 2}, @@ -1874,7 +1874,7 @@ static state states_81[2] = { {1, arcs_81_1}, }; static arc arcs_82_0[1] = { - {23, 1}, + {21, 1}, }; static arc arcs_82_1[1] = { {98, 2}, @@ -1901,7 +1901,7 @@ static state states_82[6] = { {1, arcs_82_5}, }; static arc arcs_83_0[2] = { - {16, 1}, + {38, 1}, {177, 2}, }; static arc arcs_83_1[1] = { @@ -1916,7 +1916,7 @@ static state states_83[3] = { {1, arcs_83_2}, }; static arc arcs_84_0[1] = { - {26, 1}, + {24, 1}, }; static arc arcs_84_1[1] = { {133, 2}, @@ -1945,7 +1945,7 @@ static state states_85[2] = { {1, arcs_85_1}, }; static arc arcs_86_0[1] = { - {37, 1}, + {35, 1}, }; static arc arcs_86_1[2] = { {179, 2}, @@ -1960,7 +1960,7 @@ static state states_86[3] = { {1, arcs_86_2}, }; static arc arcs_87_0[2] = { - {24, 1}, + {22, 1}, {80, 2}, }; static arc arcs_87_1[1] = { @@ -2115,7 +2115,7 @@ static dfa dfas[92] = { {257, "file_input", 0, 2, states_1, "\344\377\377\377\377\027\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {258, "eval_input", 0, 3, states_2, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {259, "decorator", 0, 7, states_3, "\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {260, "decorators", 0, 2, states_4, @@ -2123,9 +2123,9 @@ static dfa dfas[92] = { {261, "decorated", 0, 3, states_5, "\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {262, "async_funcdef", 0, 3, states_6, - "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {263, "funcdef", 0, 9, states_7, - "\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {264, "parameters", 0, 4, states_8, "\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {265, "typedargslist", 0, 22, states_9, @@ -2139,39 +2139,39 @@ static dfa dfas[92] = { {269, "stmt", 0, 2, states_13, "\340\377\377\377\377\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {270, "simple_stmt", 0, 4, states_14, - "\340\373\126\373\343\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\373\325\376\270\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {271, "small_stmt", 0, 2, states_15, - "\340\373\126\373\343\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\373\325\376\270\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {272, "expr_stmt", 0, 6, states_16, - "\340\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {273, "annassign", 0, 5, states_17, "\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {274, "testlist_star_expr", 0, 3, states_18, - "\340\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {275, "augassign", 0, 2, states_19, "\000\000\000\000\000\000\000\000\000\000\340\377\003\000\000\000\000\000\000\000\000\000\000"}, {276, "del_stmt", 0, 3, states_20, - "\000\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {277, "pass_stmt", 0, 2, states_21, - "\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {278, "flow_stmt", 0, 2, states_22, - "\000\000\024\000\043\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\005\300\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {279, "break_stmt", 0, 2, states_23, - "\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {280, "continue_stmt", 0, 2, states_24, - "\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {281, "return_stmt", 0, 3, states_25, - "\000\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {282, "yield_stmt", 0, 2, states_26, - "\000\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {283, "raise_stmt", 0, 5, states_27, - "\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {284, "import_stmt", 0, 2, states_28, - "\000\000\000\011\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\100\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {285, "import_name", 0, 3, states_29, - "\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {286, "import_from", 0, 8, states_30, - "\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {287, "import_as_name", 0, 4, states_31, "\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {288, "dotted_as_name", 0, 4, states_32, @@ -2183,117 +2183,117 @@ static dfa dfas[92] = { {291, "dotted_name", 0, 2, states_35, "\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {292, "global_stmt", 0, 3, states_36, - "\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {293, "nonlocal_stmt", 0, 3, states_37, - "\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {294, "assert_stmt", 0, 5, states_38, "\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {295, "compound_stmt", 0, 2, states_39, - "\000\004\251\004\034\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\004\052\001\107\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {296, "async_stmt", 0, 3, states_40, - "\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {297, "if_stmt", 0, 8, states_41, - "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {298, "while_stmt", 0, 8, states_42, - "\000\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {299, "for_stmt", 0, 11, states_43, - "\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {300, "try_stmt", 0, 13, states_44, - "\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {301, "with_stmt", 0, 6, states_45, - "\000\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {302, "with_item", 0, 4, states_46, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {303, "except_clause", 0, 5, states_47, "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000"}, {304, "suite", 0, 5, states_48, - "\344\373\126\373\343\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\344\373\325\376\270\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {305, "namedexpr_test", 0, 4, states_49, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {306, "test", 0, 6, states_50, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {307, "test_nocond", 0, 2, states_51, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {308, "lambdef", 0, 5, states_52, - "\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {309, "lambdef_nocond", 0, 5, states_53, - "\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {310, "or_test", 0, 2, states_54, - "\240\173\002\100\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\020\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {311, "and_test", 0, 2, states_55, - "\240\173\002\100\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\020\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {312, "not_test", 0, 3, states_56, - "\240\173\002\100\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\020\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {313, "comparison", 0, 2, states_57, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {314, "comp_op", 0, 4, states_58, - "\000\000\000\100\000\000\000\000\000\000\000\000\000\000\000\004\000\340\017\000\000\000\000"}, + "\000\000\000\020\000\000\000\000\000\000\000\000\000\000\000\004\000\340\017\000\000\000\000"}, {315, "star_expr", 0, 3, states_59, "\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {316, "expr", 0, 2, states_60, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {317, "xor_expr", 0, 2, states_61, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {318, "and_expr", 0, 2, states_62, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {319, "shift_expr", 0, 2, states_63, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {320, "arith_expr", 0, 2, states_64, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {321, "term", 0, 2, states_65, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {322, "factor", 0, 3, states_66, - "\240\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {323, "power", 0, 4, states_67, - "\040\172\002\000\100\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\040\172\000\000\220\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {324, "atom_expr", 0, 3, states_68, - "\040\172\002\000\100\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\040\172\000\000\220\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {325, "atom", 0, 9, states_69, - "\040\172\000\000\100\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\040\172\000\000\020\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {326, "testlist_comp", 0, 5, states_70, - "\340\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {327, "trailer", 0, 7, states_71, "\040\100\000\000\000\000\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\000\000"}, {328, "subscriptlist", 0, 3, states_72, - "\240\173\002\120\300\007\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {329, "subscript", 0, 5, states_73, - "\240\173\002\120\300\007\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {330, "sliceop", 0, 3, states_74, "\000\000\000\000\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {331, "exprlist", 0, 3, states_75, - "\340\173\002\000\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\000\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {332, "testlist", 0, 3, states_76, - "\240\173\002\120\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\240\173\000\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {333, "dictorsetmaker", 0, 14, states_77, - "\340\173\002\120\300\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {334, "classdef", 0, 8, states_78, - "\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {335, "arglist", 0, 3, states_79, - "\340\173\002\120\300\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {336, "argument", 0, 4, states_80, - "\340\173\002\120\300\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {337, "comp_iter", 0, 2, states_81, - "\000\000\201\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\040\001\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {338, "sync_comp_for", 0, 6, states_82, - "\000\000\200\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {339, "comp_for", 0, 3, states_83, - "\000\000\201\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\040\000\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {340, "comp_if", 0, 4, states_84, - "\000\000\000\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {341, "encoding_decl", 0, 2, states_85, "\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {342, "yield_expr", 0, 3, states_86, - "\000\000\000\000\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\000\000\000\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {343, "yield_arg", 0, 3, states_87, - "\340\173\002\121\300\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\100\024\260\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {344, "func_body_suite", 0, 7, states_88, - "\344\373\126\373\343\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\344\373\325\376\270\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {345, "func_type_input", 0, 3, states_89, "\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {346, "func_type", 0, 6, states_90, "\040\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, {347, "typelist", 0, 11, states_91, - "\340\173\002\120\300\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, + "\340\173\000\024\260\007\000\000\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, }; static label labels[183] = { {0, "EMPTY"}, @@ -2312,8 +2312,6 @@ static label labels[183] = { {1, "True"}, {9, 0}, {1, "assert"}, - {1, "async"}, - {1, "await"}, {1, "break"}, {1, "class"}, {1, "continue"}, @@ -2336,6 +2334,8 @@ static label labels[183] = { {1, "yield"}, {25, 0}, {31, 0}, + {56, 0}, + {55, 0}, {1, 0}, {2, 0}, {3, 0}, @@ -2357,7 +2357,7 @@ static label labels[183] = { {51, 0}, {11, 0}, {306, 0}, - {56, 0}, + {58, 0}, {344, 0}, {265, 0}, {35, 0}, diff --git a/Python/pythonrun.c b/Python/pythonrun.c index ae6778d8a58638..b763c99da01378 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1200,6 +1200,8 @@ PyParser_ASTFromStringObject(const char *s, PyObject *filename, int start, PyCompilerFlags localflags; perrdetail err; int iflags = PARSER_FLAGS(flags); + if (flags->cf_feature_version < 7) + iflags |= PyPARSE_ASYNC_HACKS; node *n = PyParser_ParseStringObject(s, filename, &_PyParser_Grammar, start, &err, From 1851eafa63415e8dd19dc2cf78d0cac84f2fd55e Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 16:49:30 -0800 Subject: [PATCH 04/14] Add blurb --- .../2019-02-27-16-49-08.bpo-35975.IescLY.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-27-16-49-08.bpo-35975.IescLY.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-27-16-49-08.bpo-35975.IescLY.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-27-16-49-08.bpo-35975.IescLY.rst new file mode 100644 index 00000000000000..3bbfc74469c902 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-27-16-49-08.bpo-35975.IescLY.rst @@ -0,0 +1,7 @@ +Add a ``feature_version`` flag to ``ast.parse()`` (documented) and +``compile()`` (hidden) that allows tweaking the parser to support older +versions of the grammar. In particular, if ``feature_version`` is 5 or 6, +the hacks for the ``async`` and ``await`` keyword from PEP 492 are +reinstated. (For 7 or higher, these are unconditionally treated as keywords, +but they are still special tokens rather than ``NAME`` tokens that the +parser driver recognizes.) From 00fdd08bc1160510d8ed3d7a2d82bdd88e148a47 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 17:04:28 -0800 Subject: [PATCH 05/14] Check flags isn't NULL before dereferencing (this broke regen-importlib) --- Python/pythonrun.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index b763c99da01378..199ea82434e9e7 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -1200,7 +1200,7 @@ PyParser_ASTFromStringObject(const char *s, PyObject *filename, int start, PyCompilerFlags localflags; perrdetail err; int iflags = PARSER_FLAGS(flags); - if (flags->cf_feature_version < 7) + if (flags && flags->cf_feature_version < 7) iflags |= PyPARSE_ASYNC_HACKS; node *n = PyParser_ParseStringObject(s, filename, From 543ab7597db93cdbf3e347552675df0e3c54e9dd Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 20:07:02 -0800 Subject: [PATCH 06/14] 'async' and 'await' are no longer plain keywords -- fix test_keyword.py --- Lib/keyword.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/keyword.py b/Lib/keyword.py index 431991dcf4ace6..6e1e882a91e700 100755 --- a/Lib/keyword.py +++ b/Lib/keyword.py @@ -20,8 +20,6 @@ 'and', 'as', 'assert', - 'async', - 'await', 'break', 'class', 'continue', From 9f4cb2392439243c94caa76614b1330ed2357334 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 27 Feb 2019 22:06:18 -0800 Subject: [PATCH 07/14] Fix test_parser.py The PyST_Object header in parsermodule.c became one int larger because it contains a PyCompilerFlags struct, which grew extra space for the st_feature_version field. Took me long enough! --- Lib/test/test_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_parser.py b/Lib/test/test_parser.py index 0afeb322e9b511..5548a871c0244f 100644 --- a/Lib/test/test_parser.py +++ b/Lib/test/test_parser.py @@ -916,7 +916,7 @@ def XXXROUNDUP(n): return (n + 3) & ~3 return 1 << (n - 1).bit_length() - basesize = support.calcobjsize('Pii') + basesize = support.calcobjsize('Piii') nodesize = struct.calcsize('hP3iP0h2i') def sizeofchildren(node): if node is None: From 855e07bc6624ee75132edac153da3912220e88fe Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 28 Feb 2019 07:46:22 -0800 Subject: [PATCH 08/14] Manually add async and await to kwlist in keywords.py, since they are not regular keywords --- Lib/keyword.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/keyword.py b/Lib/keyword.py index 6e1e882a91e700..150c2bc46d229c 100755 --- a/Lib/keyword.py +++ b/Lib/keyword.py @@ -50,6 +50,10 @@ #--end keywords-- ] +kwlist.append('async') +kwlist.append('await') +kwlist.sort() + iskeyword = frozenset(kwlist).__contains__ def main(): From ee95fef4b76a1d088cc717673130d992344282bf Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Mar 2019 11:31:52 -0800 Subject: [PATCH 09/14] Simplify compile() call in ast.parse() --- Lib/ast.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 819be1f4c3cabe..64e7a2551fb1aa 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -38,10 +38,7 @@ def parse(source, filename='', mode='exec', *, if type_comments: flags |= PyCF_TYPE_COMMENTS return compile(source, filename, mode, flags, - dont_inherit=False, - optimize=-1, - feature_version=feature_version, - ) + feature_version=feature_version) def literal_eval(node_or_string): From ae36b7b34f8f672fc5f06e60930cf199584b18ff Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Mar 2019 11:35:21 -0800 Subject: [PATCH 10/14] Fix indentation of many ast_error() calls --- Python/ast.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Python/ast.c b/Python/ast.c index 2741926d67a78b..d4ee1b351fe16f 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -979,7 +979,7 @@ get_operator(struct compiling *c, const node *n) case AT: if (c->c_feature_version < 5) { ast_error(c, n, - "The '@' operator is only supported in Python 3.5 and greater"); + "The '@' operator is only supported in Python 3.5 and greater"); return (operator_ty)0; } return MatMult; @@ -1218,7 +1218,7 @@ ast_for_augassign(struct compiling *c, const node *n) case '@': if (c->c_feature_version < 5) { ast_error(c, n, - "The '@' operator is only supported in Python 3.5 and greater"); + "The '@' operator is only supported in Python 3.5 and greater"); return (operator_ty)0; } return MatMult; @@ -1530,7 +1530,7 @@ ast_for_arguments(struct compiling *c, const node *n) } else if (found_default) { ast_error(c, n, - "non-default argument follows default argument"); + "non-default argument follows default argument"); return NULL; } arg = ast_for_arg(c, ch); @@ -2060,7 +2060,7 @@ ast_for_comprehension(struct compiling *c, const node *n) /* Async comprehensions only allowed in Python 3.6 and greater */ if (is_async && c->c_feature_version < 6) { ast_error(c, n, - "Async comprehensions are only supported in Python 3.6 and greater"); + "Async comprehensions are only supported in Python 3.6 and greater"); return NULL; } @@ -2363,7 +2363,7 @@ ast_for_atom(struct compiling *c, const node *n) /* Check for underscores here rather than in parse_number so we can report a line number on error */ if (c->c_feature_version < 6 && strchr(STR(ch), '_') != NULL) { ast_error(c, ch, - "Underscores in numeric literals are only supported in Python 3.6 and greater"); + "Underscores in numeric literals are only supported in Python 3.6 and greater"); return NULL; } pynum = parsenumber(c, STR(ch)); @@ -2449,8 +2449,8 @@ ast_for_atom(struct compiling *c, const node *n) TYPE(CHILD(ch, 3 - is_dict)) == comp_for) { /* It's a dictionary comprehension. */ if (is_dict) { - ast_error(c, n, "dict unpacking cannot be used in " - "dict comprehension"); + ast_error(c, n, + "dict unpacking cannot be used in dict comprehension"); return NULL; } res = ast_for_dictcomp(c, ch); @@ -2710,7 +2710,7 @@ ast_for_atom_expr(struct compiling *c, const node *n) if (TYPE(CHILD(n, 0)) == AWAIT) { if (c->c_feature_version < 5) { ast_error(c, n, - "Await expressions are only supported in Python 3.5 and greater"); + "Await expressions are only supported in Python 3.5 and greater"); return NULL; } start = 1; @@ -3270,7 +3270,7 @@ ast_for_expr_stmt(struct compiling *c, const node *n) /* AnnAssigns are only allowed in Python 3.6 or greater */ if (c->c_feature_version < 6) { ast_error(c, ch, - "Variable annotation syntax is only supported in Python 3.6 and greater"); + "Variable annotation syntax is only supported in Python 3.6 and greater"); return NULL; } @@ -4094,7 +4094,7 @@ ast_for_for_stmt(struct compiling *c, const node *n0, bool is_async) if (is_async && c->c_feature_version < 5) { ast_error(c, n, - "Async for loops are only supported in Python 3.5 and greater"); + "Async for loops are only supported in Python 3.5 and greater"); return NULL; } @@ -4328,7 +4328,7 @@ ast_for_with_stmt(struct compiling *c, const node *n0, bool is_async) if (is_async && c->c_feature_version < 5) { ast_error(c, n, - "Async with statements are only supported in Python 3.5 and greater"); + "Async with statements are only supported in Python 3.5 and greater"); return NULL; } From bae211cdf25930afa729c75e679a996053ed0105 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 5 Mar 2019 15:19:28 -0800 Subject: [PATCH 11/14] Add tests for feature_version --- Lib/test/test_type_comments.py | 135 +++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 41 deletions(-) diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 3065ddca2d9ab2..f0ae4ff9d9c3a6 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -1,4 +1,5 @@ import ast +import sys import unittest @@ -20,6 +21,25 @@ async def bar(): # type: () -> int return await bar() """ +asyncvar = """\ +async = 12 +await = 13 +""" + +asynccomp = """\ +async def foo(xs): + [x async for x in xs] +""" + +matmul = """\ +a = b @ c +""" + +fstring = """\ +a = 42 +f"{a}" +""" + redundantdef = """\ def foo(): # type: () -> int # type: () -> str @@ -155,80 +175,113 @@ def favk( class TypeCommentTests(unittest.TestCase): - def parse(self, source): - return ast.parse(source, type_comments=True) + lowest = 4 # Lowest minor version supported + highest = sys.version_info[1] # Highest minor version + + def parse(self, source, feature_version=highest): + return ast.parse(source, type_comments=True, + feature_version=feature_version) + + def parses(self, source, minver=lowest, maxver=highest, expected_regex=""): + for feature_version in range(self.lowest, self.highest + 1): + if minver <= feature_version <= maxver: + try: + yield self.parse(source, feature_version) + except SyntaxError as err: + raise SyntaxError(str(err) + f" feature_version={feature_version}") + else: + with self.assertRaisesRegex(SyntaxError, expected_regex, + msg=f"feature_version={feature_version}"): + self.parse(source, feature_version) def classic_parse(self, source): return ast.parse(source) def test_funcdef(self): - tree = self.parse(funcdef) - self.assertEqual(tree.body[0].type_comment, "() -> int") - self.assertEqual(tree.body[1].type_comment, "() -> None") + for tree in self.parses(funcdef): + self.assertEqual(tree.body[0].type_comment, "() -> int") + self.assertEqual(tree.body[1].type_comment, "() -> None") tree = self.classic_parse(funcdef) self.assertEqual(tree.body[0].type_comment, None) self.assertEqual(tree.body[1].type_comment, None) def test_asyncdef(self): - tree = self.parse(asyncdef) - self.assertEqual(tree.body[0].type_comment, "() -> int") - self.assertEqual(tree.body[1].type_comment, "() -> int") + for tree in self.parses(asyncdef, minver=5): + self.assertEqual(tree.body[0].type_comment, "() -> int") + self.assertEqual(tree.body[1].type_comment, "() -> int") tree = self.classic_parse(asyncdef) self.assertEqual(tree.body[0].type_comment, None) self.assertEqual(tree.body[1].type_comment, None) + def test_asyncvar(self): + for tree in self.parses(asyncvar, maxver=6): + pass + + def test_asynccomp(self): + for tree in self.parses(asynccomp, minver=6): + pass + + def test_matmul(self): + for tree in self.parses(matmul, minver=5): + pass + + def test_fstring(self): + for tree in self.parses(fstring, minver=6): + pass + def test_redundantdef(self): - with self.assertRaisesRegex(SyntaxError, "^Cannot have two type comments on def"): - tree = self.parse(redundantdef) + for tree in self.parses(redundantdef, maxver=0, + expected_regex="^Cannot have two type comments on def"): + pass def test_nonasciidef(self): - tree = self.parse(nonasciidef) - self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt") + for tree in self.parses(nonasciidef): + self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt") def test_forstmt(self): - tree = self.parse(forstmt) - self.assertEqual(tree.body[0].type_comment, "int") + for tree in self.parses(forstmt): + self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(forstmt) self.assertEqual(tree.body[0].type_comment, None) def test_withstmt(self): - tree = self.parse(withstmt) - self.assertEqual(tree.body[0].type_comment, "int") + for tree in self.parses(withstmt): + self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(withstmt) self.assertEqual(tree.body[0].type_comment, None) def test_vardecl(self): - tree = self.parse(vardecl) - self.assertEqual(tree.body[0].type_comment, "int") + for tree in self.parses(vardecl): + self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(vardecl) self.assertEqual(tree.body[0].type_comment, None) def test_ignores(self): - tree = self.parse(ignores) - self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5]) + for tree in self.parses(ignores): + self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5]) tree = self.classic_parse(ignores) self.assertEqual(tree.type_ignores, []) def test_longargs(self): - tree = self.parse(longargs) - for t in tree.body: - # The expected args are encoded in the function name - todo = set(t.name[1:]) - self.assertEqual(len(t.args.args), - len(todo) - bool(t.args.vararg) - bool(t.args.kwarg)) - self.assertTrue(t.name.startswith('f'), t.name) - for c in t.name[1:]: - todo.remove(c) - if c == 'v': - arg = t.args.vararg - elif c == 'k': - arg = t.args.kwarg - else: - assert 0 <= ord(c) - ord('a') < len(t.args.args) - arg = t.args.args[ord(c) - ord('a')] - self.assertEqual(arg.arg, c) # That's the argument name - self.assertEqual(arg.type_comment, arg.arg.upper()) - assert not todo + for tree in self.parses(longargs): + for t in tree.body: + # The expected args are encoded in the function name + todo = set(t.name[1:]) + self.assertEqual(len(t.args.args), + len(todo) - bool(t.args.vararg) - bool(t.args.kwarg)) + self.assertTrue(t.name.startswith('f'), t.name) + for c in t.name[1:]: + todo.remove(c) + if c == 'v': + arg = t.args.vararg + elif c == 'k': + arg = t.args.kwarg + else: + assert 0 <= ord(c) - ord('a') < len(t.args.args) + arg = t.args.args[ord(c) - ord('a')] + self.assertEqual(arg.arg, c) # That's the argument name + self.assertEqual(arg.type_comment, arg.arg.upper()) + assert not todo tree = self.classic_parse(longargs) for t in tree.body: for arg in t.args.args + [t.args.vararg, t.args.kwarg]: @@ -247,8 +300,8 @@ def test_inappropriate_type_comments(self): def check_both_ways(source): ast.parse(source, type_comments=False) - with self.assertRaises(SyntaxError): - ast.parse(source, type_comments=True) + for tree in self.parses(source, maxver=0): + pass check_both_ways("pass # type: int\n") check_both_ways("foo() # type: int\n") From dbb08ccc4bc34618a49582d7247ee4b75c5cccd2 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Mar 2019 12:06:36 -0800 Subject: [PATCH 12/14] Clarify range of feature_version in docs. --- Doc/library/ast.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 742238611628ce..1884bea80e8047 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -148,7 +148,8 @@ and classes for traversing abstract syntax trees: Also, setting ``feature_version`` to the minor version of an earlier Python 3 version will attempt to parse using that version's grammar. For example, setting ``feature_version=4`` will allow - the use of ``async`` and ``await`` as variable names. + the use of ``async`` and ``await`` as variable names. The lowest + supported value is 4; the highest is ``sys.version_info[1]``. .. warning:: It is possible to crash the Python interpreter with a From c8c84f44b66b087eadde48f9b2e033bf289cf353 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Mar 2019 12:08:15 -0800 Subject: [PATCH 13/14] Rename parses() to parse_all() --- Lib/test/test_type_comments.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index f0ae4ff9d9c3a6..7f71ee851d408b 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -182,7 +182,7 @@ def parse(self, source, feature_version=highest): return ast.parse(source, type_comments=True, feature_version=feature_version) - def parses(self, source, minver=lowest, maxver=highest, expected_regex=""): + def parse_all(self, source, minver=lowest, maxver=highest, expected_regex=""): for feature_version in range(self.lowest, self.highest + 1): if minver <= feature_version <= maxver: try: @@ -198,7 +198,7 @@ def classic_parse(self, source): return ast.parse(source) def test_funcdef(self): - for tree in self.parses(funcdef): + for tree in self.parse_all(funcdef): self.assertEqual(tree.body[0].type_comment, "() -> int") self.assertEqual(tree.body[1].type_comment, "() -> None") tree = self.classic_parse(funcdef) @@ -206,7 +206,7 @@ def test_funcdef(self): self.assertEqual(tree.body[1].type_comment, None) def test_asyncdef(self): - for tree in self.parses(asyncdef, minver=5): + for tree in self.parse_all(asyncdef, minver=5): self.assertEqual(tree.body[0].type_comment, "() -> int") self.assertEqual(tree.body[1].type_comment, "() -> int") tree = self.classic_parse(asyncdef) @@ -214,56 +214,56 @@ def test_asyncdef(self): self.assertEqual(tree.body[1].type_comment, None) def test_asyncvar(self): - for tree in self.parses(asyncvar, maxver=6): + for tree in self.parse_all(asyncvar, maxver=6): pass def test_asynccomp(self): - for tree in self.parses(asynccomp, minver=6): + for tree in self.parse_all(asynccomp, minver=6): pass def test_matmul(self): - for tree in self.parses(matmul, minver=5): + for tree in self.parse_all(matmul, minver=5): pass def test_fstring(self): - for tree in self.parses(fstring, minver=6): + for tree in self.parse_all(fstring, minver=6): pass def test_redundantdef(self): - for tree in self.parses(redundantdef, maxver=0, + for tree in self.parse_all(redundantdef, maxver=0, expected_regex="^Cannot have two type comments on def"): pass def test_nonasciidef(self): - for tree in self.parses(nonasciidef): + for tree in self.parse_all(nonasciidef): self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt") def test_forstmt(self): - for tree in self.parses(forstmt): + for tree in self.parse_all(forstmt): self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(forstmt) self.assertEqual(tree.body[0].type_comment, None) def test_withstmt(self): - for tree in self.parses(withstmt): + for tree in self.parse_all(withstmt): self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(withstmt) self.assertEqual(tree.body[0].type_comment, None) def test_vardecl(self): - for tree in self.parses(vardecl): + for tree in self.parse_all(vardecl): self.assertEqual(tree.body[0].type_comment, "int") tree = self.classic_parse(vardecl) self.assertEqual(tree.body[0].type_comment, None) def test_ignores(self): - for tree in self.parses(ignores): + for tree in self.parse_all(ignores): self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5]) tree = self.classic_parse(ignores) self.assertEqual(tree.type_ignores, []) def test_longargs(self): - for tree in self.parses(longargs): + for tree in self.parse_all(longargs): for t in tree.body: # The expected args are encoded in the function name todo = set(t.name[1:]) @@ -300,7 +300,7 @@ def test_inappropriate_type_comments(self): def check_both_ways(source): ast.parse(source, type_comments=False) - for tree in self.parses(source, maxver=0): + for tree in self.parse_all(source, maxver=0): pass check_both_ways("pass # type: int\n") From e925175040f0f55b6fadf7cd03bd77fb17650b39 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 7 Mar 2019 12:13:15 -0800 Subject: [PATCH 14/14] Add test for underscore in number --- Lib/test/test_type_comments.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 7f71ee851d408b..cac6e8b25d21af 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -40,6 +40,10 @@ async def foo(xs): f"{a}" """ +underscorednumber = """\ +a = 42_42_42 +""" + redundantdef = """\ def foo(): # type: () -> int # type: () -> str @@ -229,6 +233,10 @@ def test_fstring(self): for tree in self.parse_all(fstring, minver=6): pass + def test_underscorednumber(self): + for tree in self.parse_all(underscorednumber, minver=6): + pass + def test_redundantdef(self): for tree in self.parse_all(redundantdef, maxver=0, expected_regex="^Cannot have two type comments on def"):