gh-152631: Fix ast.parse on lazy imports with older feature_version (#152632)

This commit is contained in:
sobolevn authored and GitHub committed 2026-10-25 20:35:59 +00:00
1 parent bb7e16ace9
commit 7b12a415c8
4 files changed
+182 -32

No files matched your search

+15 -5
View File
@@ -218,15 +218,25 @@ import_stmt[stmt_ty](memo):
# -----------------
import_name[stmt_ty]:
| lazy="lazy"? 'import' a=dotted_as_names { _PyAST_Import(a, lazy ? 1 : 0, EXTRA) }
| "lazy" 'import' a=dotted_as_names {
CHECK_VERSION(stmt_ty, 15, "Lazy imports are",
_PyAST_Import(a, 1, EXTRA)) }
| 'import' a=dotted_as_names { _PyAST_Import(a, 0, EXTRA) }
# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS
import_from[stmt_ty]:
| invalid_import_from
| lazy="lazy"? 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
_PyPegen_checked_from_import(p, a, b, c, lazy, EXTRA) }
| lazy="lazy"? 'from' a=('.' | '...')+ 'import' b=import_from_targets {
_PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), lazy ? 1 : 0, EXTRA) }
| lazy="lazy" 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
CHECK_VERSION(stmt_ty, 15, "Lazy imports are",
_PyPegen_checked_from_import(p, a, b, c, lazy, EXTRA)) }
| "lazy" 'from' a=('.' | '...')+ 'import' b=import_from_targets {
CHECK_VERSION(stmt_ty, 15, "Lazy imports are",
_PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), 1, EXTRA)) }
| 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
_PyPegen_checked_from_import(p, a, b, c, NULL, EXTRA) }
| 'from' a=('.' | '...')+ 'import' b=import_from_targets {
_PyAST_ImportFrom(NULL, b, _PyPegen_seq_count_dots(a), 0, EXTRA) }
import_from_targets[asdl_alias_seq*]:
| '(' a=import_from_as_names [','] ')' { a }
| import_from_as_names !','
+13
View File
@@ -1031,6 +1031,19 @@ class AST_Tests(unittest.TestCase):
with self.assertRaises(SyntaxError):
ast.parse(sample, feature_version=(3, 12))
def test_lazy_imports_feature_version(self):
samples = [
"lazy import os",
"lazy from .. import name",
"lazy from mod import some",
]
for sample in samples:
with self.subTest(sample):
ast.parse(sample)
ast.parse(sample, feature_version=(3, 15))
with self.assertRaises(SyntaxError):
ast.parse(sample, feature_version=(3, 14))
def test_invalid_major_feature_version(self):
with self.assertRaises(ValueError):
ast.parse('pass', feature_version=(2, 7))
@@ -0,0 +1,2 @@
Fix :func:`ast.parse` ability to parse lazy imports with *feature_version*
older than 3.15, now it properly raises :exc:`SyntaxError`.
+152 -27
View File
@@ -3485,7 +3485,7 @@ import_stmt_rule(Parser *p)
return _res;
}
// import_name: "lazy"? 'import' dotted_as_names
// import_name: "lazy" 'import' dotted_as_names | 'import' dotted_as_names
static stmt_ty
import_name_rule(Parser *p)
{
@@ -3507,24 +3507,24 @@ import_name_rule(Parser *p)
UNUSED(_start_lineno); // Only used by EXTRA macro
int _start_col_offset = p->tokens[_mark]->col_offset;
UNUSED(_start_col_offset); // Only used by EXTRA macro
{ // "lazy"? 'import' dotted_as_names
{ // "lazy" 'import' dotted_as_names
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_name[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'import' dotted_as_names"));
Token * _keyword;
D(fprintf(stderr, "%*c> import_name[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'import' dotted_as_names"));
expr_ty _keyword;
Token * _keyword_1;
asdl_alias_seq* a;
void *lazy;
if (
(lazy = _PyPegen_expect_soft_keyword(p, "lazy"), !p->error_indicator) // "lazy"?
(_keyword = _PyPegen_expect_soft_keyword(p, "lazy")) // soft_keyword='"lazy"'
&&
(_keyword = _PyPegen_expect_token(p, 500)) // token='import'
(_keyword_1 = _PyPegen_expect_token(p, 500)) // token='import'
&&
(a = dotted_as_names_rule(p)) // dotted_as_names
)
{
D(fprintf(stderr, "%*c+ import_name[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'import' dotted_as_names"));
D(fprintf(stderr, "%*c+ import_name[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'import' dotted_as_names"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
@@ -3534,7 +3534,7 @@ import_name_rule(Parser *p)
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = _PyAST_Import ( a , lazy ? 1 : 0 , EXTRA );
_res = CHECK_VERSION ( stmt_ty , 15 , "Lazy imports are" , _PyAST_Import ( a , 1 , EXTRA ) );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -3544,7 +3544,43 @@ import_name_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_name[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\"? 'import' dotted_as_names"));
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\" 'import' dotted_as_names"));
}
{ // 'import' dotted_as_names
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_name[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'import' dotted_as_names"));
Token * _keyword;
asdl_alias_seq* a;
if (
(_keyword = _PyPegen_expect_token(p, 500)) // token='import'
&&
(a = dotted_as_names_rule(p)) // dotted_as_names
)
{
D(fprintf(stderr, "%*c+ import_name[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'import' dotted_as_names"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
return NULL;
}
int _end_lineno = _token->end_lineno;
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = _PyAST_Import ( a , 0 , EXTRA );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
return NULL;
}
goto done;
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_name[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'import' dotted_as_names"));
}
_res = NULL;
done:
@@ -3554,8 +3590,10 @@ import_name_rule(Parser *p)
// import_from:
// | invalid_import_from
// | "lazy"? 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
// | "lazy"? 'from' (('.' | '...'))+ 'import' import_from_targets
// | "lazy" 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
// | "lazy" 'from' (('.' | '...'))+ 'import' import_from_targets
// | 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
// | 'from' (('.' | '...'))+ 'import' import_from_targets
static stmt_ty
import_from_rule(Parser *p)
{
@@ -3596,20 +3634,20 @@ import_from_rule(Parser *p)
D(fprintf(stderr, "%*c%s import_from[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_import_from"));
}
{ // "lazy"? 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
{ // "lazy" 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
Token * _keyword;
Token * _keyword_1;
asdl_seq * a;
expr_ty b;
asdl_alias_seq* c;
void *lazy;
expr_ty lazy;
if (
(lazy = _PyPegen_expect_soft_keyword(p, "lazy"), !p->error_indicator) // "lazy"?
(lazy = _PyPegen_expect_soft_keyword(p, "lazy")) // soft_keyword='"lazy"'
&&
(_keyword = _PyPegen_expect_token(p, 501)) // token='from'
&&
@@ -3622,7 +3660,7 @@ import_from_rule(Parser *p)
(c = import_from_targets_rule(p)) // import_from_targets
)
{
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
@@ -3632,7 +3670,7 @@ import_from_rule(Parser *p)
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = _PyPegen_checked_from_import ( p , a , b , c , lazy , EXTRA );
_res = CHECK_VERSION ( stmt_ty , 15 , "Lazy imports are" , _PyPegen_checked_from_import ( p , a , b , c , lazy , EXTRA ) );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -3642,22 +3680,109 @@ import_from_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_from[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
}
{ // "lazy"? 'from' (('.' | '...'))+ 'import' import_from_targets
{ // "lazy" 'from' (('.' | '...'))+ 'import' import_from_targets
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))+ 'import' import_from_targets"));
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))+ 'import' import_from_targets"));
expr_ty _keyword;
Token * _keyword_1;
Token * _keyword_2;
asdl_seq * a;
asdl_alias_seq* b;
if (
(_keyword = _PyPegen_expect_soft_keyword(p, "lazy")) // soft_keyword='"lazy"'
&&
(_keyword_1 = _PyPegen_expect_token(p, 501)) // token='from'
&&
(a = _loop1_18_rule(p)) // (('.' | '...'))+
&&
(_keyword_2 = _PyPegen_expect_token(p, 500)) // token='import'
&&
(b = import_from_targets_rule(p)) // import_from_targets
)
{
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))+ 'import' import_from_targets"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
return NULL;
}
int _end_lineno = _token->end_lineno;
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = CHECK_VERSION ( stmt_ty , 15 , "Lazy imports are" , _PyAST_ImportFrom ( NULL , b , _PyPegen_seq_count_dots ( a ) , 1 , EXTRA ) );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
return NULL;
}
goto done;
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_from[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\" 'from' (('.' | '...'))+ 'import' import_from_targets"));
}
{ // 'from' (('.' | '...'))* dotted_name 'import' import_from_targets
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
Token * _keyword;
Token * _keyword_1;
asdl_seq * a;
expr_ty b;
asdl_alias_seq* c;
if (
(_keyword = _PyPegen_expect_token(p, 501)) // token='from'
&&
(a = _loop0_17_rule(p)) // (('.' | '...'))*
&&
(b = dotted_name_rule(p)) // dotted_name
&&
(_keyword_1 = _PyPegen_expect_token(p, 500)) // token='import'
&&
(c = import_from_targets_rule(p)) // import_from_targets
)
{
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
return NULL;
}
int _end_lineno = _token->end_lineno;
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = _PyPegen_checked_from_import ( p , a , b , c , NULL , EXTRA );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
return NULL;
}
goto done;
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_from[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'from' (('.' | '...'))* dotted_name 'import' import_from_targets"));
}
{ // 'from' (('.' | '...'))+ 'import' import_from_targets
if (p->error_indicator) {
p->level--;
return NULL;
}
D(fprintf(stderr, "%*c> import_from[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'from' (('.' | '...'))+ 'import' import_from_targets"));
Token * _keyword;
Token * _keyword_1;
asdl_seq * a;
asdl_alias_seq* b;
void *lazy;
if (
(lazy = _PyPegen_expect_soft_keyword(p, "lazy"), !p->error_indicator) // "lazy"?
&&
(_keyword = _PyPegen_expect_token(p, 501)) // token='from'
&&
(a = _loop1_18_rule(p)) // (('.' | '...'))+
@@ -3667,7 +3792,7 @@ import_from_rule(Parser *p)
(b = import_from_targets_rule(p)) // import_from_targets
)
{
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))+ 'import' import_from_targets"));
D(fprintf(stderr, "%*c+ import_from[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'from' (('.' | '...'))+ 'import' import_from_targets"));
Token *_token = _PyPegen_get_last_nonnwhitespace_token(p);
if (_token == NULL) {
p->level--;
@@ -3677,7 +3802,7 @@ import_from_rule(Parser *p)
UNUSED(_end_lineno); // Only used by EXTRA macro
int _end_col_offset = _token->end_col_offset;
UNUSED(_end_col_offset); // Only used by EXTRA macro
_res = _PyAST_ImportFrom ( NULL , b , _PyPegen_seq_count_dots ( a ) , lazy ? 1 : 0 , EXTRA );
_res = _PyAST_ImportFrom ( NULL , b , _PyPegen_seq_count_dots ( a ) , 0 , EXTRA );
if ((_res == NULL || p->error_indicator) && PyErr_Occurred()) {
p->error_indicator = 1;
p->level--;
@@ -3687,7 +3812,7 @@ import_from_rule(Parser *p)
}
p->mark = _mark;
D(fprintf(stderr, "%*c%s import_from[%d-%d]: %s failed!\n", p->level, ' ',
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"lazy\"? 'from' (('.' | '...'))+ 'import' import_from_targets"));
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'from' (('.' | '...'))+ 'import' import_from_targets"));
}
_res = NULL;
done: