Skip to content

Commit 20b14d9

Browse files
pablogsalDinoV
authored andcommitted
Syntax restrictions for lazy imports
1 parent 07a633f commit 20b14d9

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

Include/internal/pycore_symtable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ typedef struct _symtable_entry {
126126
unsigned ste_method : 1; /* true if block is a function block defined in class scope */
127127
unsigned ste_has_conditional_annotations : 1; /* true if block has conditionally executed annotations */
128128
unsigned ste_in_conditional_block : 1; /* set while we are inside a conditionally executed block */
129+
unsigned ste_in_try_block : 1; /* set while we are inside a try/except block */
129130
unsigned ste_in_unevaluated_annotation : 1; /* set while we are processing an annotation that will not be evaluated */
130131
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
131132
_Py_SourceLocation ste_loc; /* source location of block */

Lib/test/test_syntax.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,6 +3394,119 @@ def test_ifexp_body_stmt_else_stmt(self):
33943394
]:
33953395
self._check_error(f"x = {lhs_stmt} if 1 else {rhs_stmt}", msg)
33963396

3397+
3398+
class LazyImportRestrictionTestCase(SyntaxErrorTestCase):
3399+
"""Test syntax restrictions for lazy imports."""
3400+
3401+
def test_lazy_import_in_try_block(self):
3402+
"""Test that lazy imports are not allowed inside try blocks."""
3403+
self._check_error("""\
3404+
try:
3405+
lazy import os
3406+
except:
3407+
pass
3408+
""", "lazy import not allowed inside try/except blocks")
3409+
3410+
self._check_error("""\
3411+
try:
3412+
lazy from sys import path
3413+
except ImportError:
3414+
pass
3415+
""", "lazy from ... import not allowed inside try/except blocks")
3416+
3417+
def test_lazy_import_in_trystar_block(self):
3418+
"""Test that lazy imports are not allowed inside try* blocks."""
3419+
self._check_error("""\
3420+
try:
3421+
lazy import json
3422+
except* Exception:
3423+
pass
3424+
""", "lazy import not allowed inside try/except blocks")
3425+
3426+
self._check_error("""\
3427+
try:
3428+
lazy from collections import defaultdict
3429+
except* ImportError:
3430+
pass
3431+
""", "lazy from ... import not allowed inside try/except blocks")
3432+
3433+
def test_lazy_import_in_function(self):
3434+
"""Test that lazy imports are not allowed inside functions."""
3435+
self._check_error("""\
3436+
def func():
3437+
lazy import math
3438+
""", "lazy import not allowed inside functions")
3439+
3440+
self._check_error("""\
3441+
def func():
3442+
lazy from datetime import datetime
3443+
""", "lazy from ... import not allowed inside functions")
3444+
3445+
def test_lazy_import_in_async_function(self):
3446+
"""Test that lazy imports are not allowed inside async functions."""
3447+
self._check_error("""\
3448+
async def async_func():
3449+
lazy import asyncio
3450+
""", "lazy import not allowed inside functions")
3451+
3452+
self._check_error("""\
3453+
async def async_func():
3454+
lazy from json import loads
3455+
""", "lazy from ... import not allowed inside functions")
3456+
3457+
def test_lazy_import_in_class(self):
3458+
"""Test that lazy imports are not allowed inside classes."""
3459+
self._check_error("""\
3460+
class MyClass:
3461+
lazy import typing
3462+
""", "lazy import not allowed inside classes")
3463+
3464+
self._check_error("""\
3465+
class MyClass:
3466+
lazy from abc import ABC
3467+
""", "lazy from ... import not allowed inside classes")
3468+
3469+
def test_lazy_import_star_forbidden(self):
3470+
"""Test that 'lazy from ... import *' is forbidden everywhere."""
3471+
# At module level should also be forbidden
3472+
self._check_error("lazy from os import *",
3473+
"lazy from ... import \\* is not allowed")
3474+
3475+
# Inside function should give lazy function error first
3476+
self._check_error("""\
3477+
def func():
3478+
lazy from sys import *
3479+
""", "lazy from ... import not allowed inside functions")
3480+
3481+
def test_lazy_import_nested_scopes(self):
3482+
"""Test lazy imports in nested scopes."""
3483+
self._check_error("""\
3484+
class Outer:
3485+
def method(self):
3486+
lazy import sys
3487+
""", "lazy import not allowed inside functions")
3488+
3489+
self._check_error("""\
3490+
def outer():
3491+
class Inner:
3492+
lazy import json
3493+
""", "lazy import not allowed inside classes")
3494+
3495+
self._check_error("""\
3496+
def outer():
3497+
def inner():
3498+
lazy from collections import deque
3499+
""", "lazy from ... import not allowed inside functions")
3500+
3501+
def test_lazy_import_valid_cases(self):
3502+
"""Test that lazy imports work at module level."""
3503+
# These should compile without errors
3504+
compile("lazy import os", "<test>", "exec")
3505+
compile("lazy from sys import path", "<test>", "exec")
3506+
compile("lazy import json as j", "<test>", "exec")
3507+
compile("lazy from datetime import datetime as dt", "<test>", "exec")
3508+
3509+
33973510
def load_tests(loader, tests, pattern):
33983511
tests.addTest(doctest.DocTestSuite())
33993512
return tests

Python/symtable.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,13 @@ symtable_enter_type_param_block(struct symtable *st, identifier name,
17471747
#define LEAVE_CONDITIONAL_BLOCK(ST) \
17481748
(ST)->st_cur->ste_in_conditional_block = in_conditional_block;
17491749

1750+
#define ENTER_TRY_BLOCK(ST) \
1751+
int in_try_block = (ST)->st_cur->ste_in_try_block; \
1752+
(ST)->st_cur->ste_in_try_block = 1;
1753+
1754+
#define LEAVE_TRY_BLOCK(ST) \
1755+
(ST)->st_cur->ste_in_try_block = in_try_block;
1756+
17501757
#define ENTER_RECURSIVE() \
17511758
if (Py_EnterRecursiveCall(" during compilation")) { \
17521759
return 0; \
@@ -1808,6 +1815,36 @@ check_import_from(struct symtable *st, stmt_ty s)
18081815
return 1;
18091816
}
18101817

1818+
static int
1819+
check_lazy_import_context(struct symtable *st, stmt_ty s, const char* import_type)
1820+
{
1821+
/* Check if inside try/except block */
1822+
if (st->st_cur->ste_in_try_block) {
1823+
PyErr_Format(PyExc_SyntaxError,
1824+
"lazy %s not allowed inside try/except blocks", import_type);
1825+
SET_ERROR_LOCATION(st->st_filename, LOCATION(s));
1826+
return 0;
1827+
}
1828+
1829+
/* Check if inside function scope */
1830+
if (st->st_cur->ste_type == FunctionBlock) {
1831+
PyErr_Format(PyExc_SyntaxError,
1832+
"lazy %s not allowed inside functions", import_type);
1833+
SET_ERROR_LOCATION(st->st_filename, LOCATION(s));
1834+
return 0;
1835+
}
1836+
1837+
/* Check if inside class scope */
1838+
if (st->st_cur->ste_type == ClassBlock) {
1839+
PyErr_Format(PyExc_SyntaxError,
1840+
"lazy %s not allowed inside classes", import_type);
1841+
SET_ERROR_LOCATION(st->st_filename, LOCATION(s));
1842+
return 0;
1843+
}
1844+
1845+
return 1;
1846+
}
1847+
18111848
static bool
18121849
allows_top_level_await(struct symtable *st)
18131850
{
@@ -2076,19 +2113,23 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
20762113
break;
20772114
case Try_kind: {
20782115
ENTER_CONDITIONAL_BLOCK(st);
2116+
ENTER_TRY_BLOCK(st);
20792117
VISIT_SEQ(st, stmt, s->v.Try.body);
20802118
VISIT_SEQ(st, excepthandler, s->v.Try.handlers);
20812119
VISIT_SEQ(st, stmt, s->v.Try.orelse);
20822120
VISIT_SEQ(st, stmt, s->v.Try.finalbody);
2121+
LEAVE_TRY_BLOCK(st);
20832122
LEAVE_CONDITIONAL_BLOCK(st);
20842123
break;
20852124
}
20862125
case TryStar_kind: {
20872126
ENTER_CONDITIONAL_BLOCK(st);
2127+
ENTER_TRY_BLOCK(st);
20882128
VISIT_SEQ(st, stmt, s->v.TryStar.body);
20892129
VISIT_SEQ(st, excepthandler, s->v.TryStar.handlers);
20902130
VISIT_SEQ(st, stmt, s->v.TryStar.orelse);
20912131
VISIT_SEQ(st, stmt, s->v.TryStar.finalbody);
2132+
LEAVE_TRY_BLOCK(st);
20922133
LEAVE_CONDITIONAL_BLOCK(st);
20932134
break;
20942135
}
@@ -2098,9 +2139,29 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
20982139
VISIT(st, expr, s->v.Assert.msg);
20992140
break;
21002141
case Import_kind:
2142+
if (s->v.Import.is_lazy) {
2143+
if (!check_lazy_import_context(st, s, "import")) {
2144+
return 0;
2145+
}
2146+
}
21012147
VISIT_SEQ(st, alias, s->v.Import.names);
21022148
break;
21032149
case ImportFrom_kind:
2150+
if (s->v.ImportFrom.is_lazy) {
2151+
if (!check_lazy_import_context(st, s, "from ... import")) {
2152+
return 0;
2153+
}
2154+
2155+
/* Check for import * */
2156+
for (Py_ssize_t i = 0; i < asdl_seq_LEN(s->v.ImportFrom.names); i++) {
2157+
alias_ty alias = (alias_ty)asdl_seq_GET(s->v.ImportFrom.names, i);
2158+
if (alias->name && _PyUnicode_EqualToASCIIString(alias->name, "*")) {
2159+
PyErr_SetString(PyExc_SyntaxError, "lazy from ... import * is not allowed");
2160+
SET_ERROR_LOCATION(st->st_filename, LOCATION(s));
2161+
return 0;
2162+
}
2163+
}
2164+
}
21042165
VISIT_SEQ(st, alias, s->v.ImportFrom.names);
21052166
if (!check_import_from(st, s)) {
21062167
return 0;

0 commit comments

Comments
 (0)