@@ -57,16 +57,89 @@ impl SemanticSyntaxChecker {
5757 } ) ;
5858 }
5959
60+ fn check_lazy_import_context < Ctx : SemanticSyntaxContext > (
61+ ctx : & Ctx ,
62+ range : TextRange ,
63+ kind : LazyImportKind ,
64+ ) -> bool {
65+ if let Some ( context) = ctx. lazy_import_context ( ) {
66+ Self :: add_error (
67+ ctx,
68+ SemanticSyntaxErrorKind :: LazyImportNotAllowed { context, kind } ,
69+ range,
70+ ) ;
71+ return true ;
72+ }
73+ false
74+ }
75+
6076 fn check_stmt < Ctx : SemanticSyntaxContext > ( & mut self , stmt : & ast:: Stmt , ctx : & Ctx ) {
6177 match stmt {
6278 Stmt :: ImportFrom ( StmtImportFrom {
6379 range,
6480 module,
6581 level,
6682 names,
83+ is_lazy,
6784 ..
6885 } ) => {
69- if matches ! ( module. as_deref( ) , Some ( "__future__" ) ) {
86+ let mut handled_lazy_error = false ;
87+
88+ if * is_lazy {
89+ // test_ok lazy_import_semantic_ok_py315
90+ // # parse_options: {"target-version": "3.15"}
91+ // import contextlib
92+ // with contextlib.nullcontext():
93+ // lazy import os
94+ // with contextlib.nullcontext():
95+ // lazy from sys import path
96+
97+ // test_err lazy_import_invalid_context_py315
98+ // # parse_options: {"target-version": "3.15"}
99+ // try:
100+ // lazy import os
101+ // except:
102+ // pass
103+ //
104+ // try:
105+ // x
106+ // except* Exception:
107+ // lazy import sys
108+ //
109+ // def func():
110+ // lazy import math
111+ //
112+ // async def async_func():
113+ // lazy from json import loads
114+ //
115+ // class MyClass:
116+ // lazy import typing
117+ //
118+ // def outer():
119+ // class Inner:
120+ // lazy import json
121+ if Self :: check_lazy_import_context ( ctx, * range, LazyImportKind :: ImportFrom ) {
122+ handled_lazy_error = true ;
123+ } else if names. iter ( ) . any ( |alias| alias. name . as_str ( ) == "*" ) {
124+ // test_err lazy_import_invalid_from_py315
125+ // # parse_options: {"target-version": "3.15"}
126+ // lazy from os import *
127+ // lazy from __future__ import annotations
128+ //
129+ // def func():
130+ // lazy from sys import *
131+ Self :: add_error ( ctx, SemanticSyntaxErrorKind :: LazyImportStar , * range) ;
132+ handled_lazy_error = true ;
133+ } else if matches ! ( module. as_deref( ) , Some ( "__future__" ) ) {
134+ Self :: add_error ( ctx, SemanticSyntaxErrorKind :: LazyFutureImport , * range) ;
135+ handled_lazy_error = true ;
136+ }
137+ }
138+
139+ if handled_lazy_error {
140+ // Skip the regular `from`-import validations after reporting the lazy-specific
141+ // syntax error with the highest precedence.
142+ } else if matches ! ( module. as_deref( ) , Some ( "__future__" ) ) {
70143 for name in names {
71144 if !is_known_future_feature ( & name. name ) {
72145 // test_ok valid_future_feature
@@ -114,6 +187,13 @@ impl SemanticSyntaxChecker {
114187 }
115188 }
116189 }
190+ Stmt :: Import ( ast:: StmtImport {
191+ range,
192+ is_lazy : true ,
193+ ..
194+ } ) => {
195+ Self :: check_lazy_import_context ( ctx, * range, LazyImportKind :: Import ) ;
196+ }
117197 Stmt :: Match ( match_stmt) => {
118198 Self :: irrefutable_match_case ( match_stmt, ctx) ;
119199 for case in & match_stmt. cases {
@@ -748,9 +828,11 @@ impl SemanticSyntaxChecker {
748828 match stmt {
749829 Stmt :: Expr ( StmtExpr { value, .. } )
750830 if !self . seen_module_docstring_boundary && value. is_string_literal_expr ( ) => { }
751- Stmt :: ImportFrom ( StmtImportFrom { module, .. } ) => {
831+ Stmt :: ImportFrom ( StmtImportFrom {
832+ module, is_lazy, ..
833+ } ) => {
752834 // Allow __future__ imports until we see a non-__future__ import.
753- if !matches ! ( module. as_deref( ) , Some ( "__future__" ) ) {
835+ if * is_lazy || !matches ! ( module. as_deref( ) , Some ( "__future__" ) ) {
754836 self . seen_futures_boundary = true ;
755837 }
756838 }
@@ -1114,6 +1196,19 @@ fn is_known_future_feature(name: &str) -> bool {
11141196 )
11151197}
11161198
1199+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash , get_size2:: GetSize ) ]
1200+ pub enum LazyImportKind {
1201+ Import ,
1202+ ImportFrom ,
1203+ }
1204+
1205+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash , get_size2:: GetSize ) ]
1206+ pub enum LazyImportContext {
1207+ Function ,
1208+ Class ,
1209+ TryExceptBlocks ,
1210+ }
1211+
11171212#[ derive( Debug , Clone , PartialEq , Eq , Hash , get_size2:: GetSize ) ]
11181213pub struct SemanticSyntaxError {
11191214 pub kind : SemanticSyntaxErrorKind ,
@@ -1231,6 +1326,24 @@ impl Display for SemanticSyntaxError {
12311326 SemanticSyntaxErrorKind :: FutureFeatureNotDefined ( name) => {
12321327 write ! ( f, "Future feature `{name}` is not defined" )
12331328 }
1329+ SemanticSyntaxErrorKind :: LazyImportNotAllowed { context, kind } => {
1330+ let statement = match kind {
1331+ LazyImportKind :: Import => "lazy import" ,
1332+ LazyImportKind :: ImportFrom => "lazy from ... import" ,
1333+ } ;
1334+ let location = match context {
1335+ LazyImportContext :: Function => "functions" ,
1336+ LazyImportContext :: Class => "classes" ,
1337+ LazyImportContext :: TryExceptBlocks => "try/except blocks" ,
1338+ } ;
1339+ write ! ( f, "{statement} not allowed inside {location}" )
1340+ }
1341+ SemanticSyntaxErrorKind :: LazyImportStar => {
1342+ f. write_str ( "lazy from ... import * is not allowed" )
1343+ }
1344+ SemanticSyntaxErrorKind :: LazyFutureImport => {
1345+ f. write_str ( "lazy from __future__ import is not allowed" )
1346+ }
12341347 SemanticSyntaxErrorKind :: BreakOutsideLoop => f. write_str ( "`break` outside loop" ) ,
12351348 SemanticSyntaxErrorKind :: ContinueOutsideLoop => f. write_str ( "`continue` outside loop" ) ,
12361349 SemanticSyntaxErrorKind :: GlobalParameter ( name) => {
@@ -1257,6 +1370,18 @@ impl Ranged for SemanticSyntaxError {
12571370
12581371#[ derive( Debug , Clone , PartialEq , Eq , Hash , get_size2:: GetSize ) ]
12591372pub enum SemanticSyntaxErrorKind {
1373+ /// Represents a `lazy` import statement in an invalid context.
1374+ LazyImportNotAllowed {
1375+ context : LazyImportContext ,
1376+ kind : LazyImportKind ,
1377+ } ,
1378+
1379+ /// Represents the use of `lazy from ... import *`.
1380+ LazyImportStar ,
1381+
1382+ /// Represents the use of `lazy from __future__ import ...`.
1383+ LazyFutureImport ,
1384+
12601385 /// Represents the use of a `__future__` import after the beginning of a file.
12611386 ///
12621387 /// ## Examples
@@ -2131,6 +2256,12 @@ pub trait SemanticSyntaxContext {
21312256 /// Returns `true` if `__future__`-style type annotations are enabled.
21322257 fn future_annotations_or_stub ( & self ) -> bool ;
21332258
2259+ /// Returns the nearest invalid context for a `lazy` import statement, if any.
2260+ ///
2261+ /// This should return the innermost relevant restriction in order of precedence:
2262+ /// function, class, then `try`/`except`.
2263+ fn lazy_import_context ( & self ) -> Option < LazyImportContext > ;
2264+
21342265 /// The target Python version for detecting backwards-incompatible syntax changes.
21352266 fn python_version ( & self ) -> PythonVersion ;
21362267
0 commit comments