Commit 495da292 authored by Guido van Rossum's avatar Guido van Rossum Committed by Miss Islington (bot)

bpo-35975: Support parsing earlier minor versions of Python 3 (GH-12086)



This adds a `feature_version` flag to `ast.parse()` (documented) and `compile()` (hidden) that allow 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.)



https://bugs.python.org/issue35975
parent bf94cc7b
......@@ -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='<unknown>', mode='exec', *, type_comments=False)
.. function:: parse(source, filename='<unknown>', 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,19 @@ 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. The lowest
supported value is 4; the highest is ``sys.version_info[1]``.
.. 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)
......
......@@ -203,6 +203,10 @@
.. data:: OP
.. data:: AWAIT
.. data:: ASYNC
.. data:: TYPE_IGNORE
.. data:: TYPE_COMMENT
......
......@@ -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).
......@@ -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
......
......@@ -55,6 +55,8 @@ ELLIPSIS '...'
COLONEQUAL ':='
OP
AWAIT
ASYNC
TYPE_IGNORE
TYPE_COMMENT
ERRORTOKEN
......
......@@ -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
......
......@@ -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
......
......@@ -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 *);
......
......@@ -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 */
......
......@@ -27,7 +27,8 @@
from _ast import *
def parse(source, filename='<unknown>', mode='exec', *, type_comments=False):
def parse(source, filename='<unknown>', 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,8 @@ def parse(source, filename='<unknown>', 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,
feature_version=feature_version)
def literal_eval(node_or_string):
......
......@@ -20,8 +20,6 @@ kwlist = [
'and',
'as',
'assert',
'async',
'await',
'break',
'class',
'continue',
......@@ -52,6 +50,10 @@ kwlist = [
#--end keywords--
]
kwlist.append('async')
kwlist.append('await')
kwlist.sort()
iskeyword = frozenset(kwlist).__contains__
def main():
......
......@@ -916,7 +916,7 @@ class STObjectTestCase(unittest.TestCase):
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:
......
import ast
import sys
import unittest
......@@ -20,6 +21,29 @@ 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}"
"""
underscorednumber = """\
a = 42_42_42
"""
redundantdef = """\
def foo(): # type: () -> int
# type: () -> str
......@@ -155,80 +179,117 @@ 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 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:
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.parse_all(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.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)
self.assertEqual(tree.body[0].type_comment, None)
self.assertEqual(tree.body[1].type_comment, None)
def test_asyncvar(self):
for tree in self.parse_all(asyncvar, maxver=6):
pass
def test_asynccomp(self):
for tree in self.parse_all(asynccomp, minver=6):
pass
def test_matmul(self):
for tree in self.parse_all(matmul, minver=5):
pass
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):
with self.assertRaisesRegex(SyntaxError, "^Cannot have two type comments on def"):
tree = self.parse(redundantdef)
for tree in self.parse_all(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.parse_all(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.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):
tree = self.parse(withstmt)
self.assertEqual(tree.body[0].type_comment, "int")
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):
tree = self.parse(vardecl)
self.assertEqual(tree.body[0].type_comment, "int")
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):
tree = self.parse(ignores)
self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5])
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):
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.parse_all(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 +308,8 @@ class TypeCommentTests(unittest.TestCase):
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.parse_all(source, maxver=0):
pass
check_both_ways("pass # type: int\n")
check_both_ways("foo() # type: int\n")
......
......@@ -58,14 +58,16 @@ RARROW = 51
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
......
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.)
......@@ -799,7 +799,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);
......
......@@ -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);
......
......@@ -1188,6 +1188,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];
......@@ -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')
......
......@@ -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);
}
......
......@@ -61,6 +61,8 @@ const char * const _PyParser_TokenNames[] = {
"ELLIPSIS",
"COLONEQUAL",
"OP",
"AWAIT",
"ASYNC",
"TYPE_IGNORE",
"TYPE_COMMENT",
"<ERRORTOKEN>",
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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];
......
......@@ -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;
......@@ -1209,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));
......@@ -1518,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);
......@@ -1719,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));
......@@ -1772,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,
......@@ -1794,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:
......@@ -1948,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) {
......@@ -2034,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 {
......@@ -2043,6 +2057,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)
......@@ -2337,7 +2358,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;
......@@ -2420,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);
......@@ -2524,7 +2553,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 +2568,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;
......@@ -2678,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);
}
......@@ -2775,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]
*/
......@@ -3233,6 +3267,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) {
......@@ -4050,6 +4091,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);
......@@ -4278,6 +4326,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;
......@@ -4768,6 +4822,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, "<fstring>",
Py_eval_input, 0);
if (!mod_n) {
......@@ -5568,6 +5623,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;
......
......@@ -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);
......
......@@ -151,7 +151,7 @@ exit:
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]*/
......@@ -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;
......
This diff is collapsed.
......@@ -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);
......@@ -1198,12 +1200,15 @@ PyParser_ASTFromStringObject(const char *s, PyObject *filename, int start,
PyCompilerFlags localflags;
perrdetail err;
int iflags = PARSER_FLAGS(flags);
if (flags && flags->cf_feature_version < 7)
iflags |= PyPARSE_ASYNC_HACKS;
node *n = PyParser_ParseStringObject(s, filename,
&_PyParser_Grammar, start, &err,
&iflags);
if (flags == NULL) {
localflags.cf_flags = 0;
localflags.cf_feature_version = PY_MINOR_VERSION;
flags = &localflags;
}
if (n) {
......@@ -1249,6 +1254,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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment