@@ -359,10 +359,12 @@ pub(crate) fn serialize_python_file(
359359 ) )
360360}
361361
362- // Bit flags for import statement metadata
362+ // Bit flags for import statement metadata. Deserializers ignore unknown bits,
363+ // so adding a flag here doesn't require a cache version bump.
363364const IMPORT_FLAG_TOP_LEVEL : u8 = 1 << 0 ; // true if import is not within a function
364365const IMPORT_FLAG_UNREACHABLE : u8 = 1 << 1 ; // true if import is in unreachable code
365366const IMPORT_FLAG_MYPY_ONLY : u8 = 1 << 2 ; // true if import is mypy-only (e.g., in TYPE_CHECKING block)
367+ const IMPORT_FLAG_LAZY : u8 = 1 << 3 ; // true if this is a lazy import (PEP 810), e.g. 'lazy import x'
366368
367369// Used to report which imports are used in a file
368370pub ( crate ) enum ImportStatement {
@@ -1616,7 +1618,7 @@ impl Ser for ast::Stmt {
16161618 ser. write_location ( a. range ( ) ) ;
16171619 }
16181620 ast:: Stmt :: Import ( i) => {
1619- let flags = make_import_flags ( ser) ;
1621+ let flags = make_import_flags ( ser, i . is_lazy ) ;
16201622 ser. write_tag ( TAG_IMPORT ) ;
16211623 // Write number of imports
16221624 ser. write_tagged_int ( i. names . len ( ) as i64 ) ;
@@ -1644,7 +1646,7 @@ impl Ser for ast::Stmt {
16441646 }
16451647 }
16461648 ast:: Stmt :: ImportFrom ( ifrom) => {
1647- let flags = make_import_flags ( ser) ;
1649+ let flags = make_import_flags ( ser, ifrom . is_lazy ) ;
16481650 // Check if this is a wildcard import (from m import *)
16491651 if ifrom. names . len ( ) == 1 && ifrom. names [ 0 ] . name . as_str ( ) == "*" {
16501652 // Serialize as ImportAll
@@ -3445,8 +3447,8 @@ fn extract_int_literal_value(expr: &ast::Expr) -> Option<i64> {
34453447 }
34463448}
34473449
3448- /// Build import flags from current serializer state
3449- fn make_import_flags ( ser : & Serializer ) -> u8 {
3450+ /// Build import flags from current serializer state and the `lazy` modifier of the statement
3451+ fn make_import_flags ( ser : & Serializer , is_lazy : bool ) -> u8 {
34503452 ( if !ser. in_function {
34513453 IMPORT_FLAG_TOP_LEVEL
34523454 } else {
@@ -3459,7 +3461,7 @@ fn make_import_flags(ser: &Serializer) -> u8 {
34593461 IMPORT_FLAG_MYPY_ONLY
34603462 } else {
34613463 0
3462- } )
3464+ } ) | ( if is_lazy { IMPORT_FLAG_LAZY } else { 0 } )
34633465}
34643466
34653467/// Serialize a list of import statements to bytes.
@@ -4051,6 +4053,44 @@ mod tests {
40514053 assert_eq ! ( bytes, expected) ;
40524054 }
40534055
4056+ /// Serialize `text` and return the import flags of each encountered import statement.
4057+ fn import_flags ( text : & str ) -> Vec < u8 > {
4058+ let ast = parse_unchecked ( text, ParseOptions :: from ( PySourceType :: Python ) ) . into_syntax ( ) ;
4059+ let mut ser = make_ser ( text) ;
4060+ ast. serialize ( & mut ser) ;
4061+ ser. imports
4062+ . iter ( )
4063+ . map ( |import| match import {
4064+ ImportStatement :: Import { flags, .. }
4065+ | ImportStatement :: ImportFrom { flags, .. }
4066+ | ImportStatement :: ImportAll { flags, .. } => * flags,
4067+ } )
4068+ . collect ( )
4069+ }
4070+
4071+ #[ test]
4072+ fn test_lazy_import_flag ( ) {
4073+ // Lazy imports (PEP 810) set IMPORT_FLAG_LAZY.
4074+ let lazy = IMPORT_FLAG_TOP_LEVEL | IMPORT_FLAG_LAZY ;
4075+ assert_eq ! ( import_flags( "lazy import foo\n " ) , [ lazy] ) ;
4076+ assert_eq ! ( import_flags( "lazy import foo as bar\n " ) , [ lazy] ) ;
4077+ assert_eq ! ( import_flags( "lazy import foo, bar\n " ) , [ lazy, lazy] ) ;
4078+ assert_eq ! ( import_flags( "lazy from foo import bar\n " ) , [ lazy] ) ;
4079+ assert_eq ! ( import_flags( "lazy from foo import bar as baz\n " ) , [ lazy] ) ;
4080+ assert_eq ! ( import_flags( "lazy from . import foo\n " ) , [ lazy] ) ;
4081+ // 'lazy from foo import *' is invalid, but we still serialize the flag.
4082+ assert_eq ! ( import_flags( "lazy from foo import *\n " ) , [ lazy] ) ;
4083+
4084+ // Regular imports don't set the flag.
4085+ let plain = IMPORT_FLAG_TOP_LEVEL ;
4086+ assert_eq ! ( import_flags( "import foo\n " ) , [ plain] ) ;
4087+ assert_eq ! ( import_flags( "from foo import bar\n " ) , [ plain] ) ;
4088+ assert_eq ! ( import_flags( "from foo import *\n " ) , [ plain] ) ;
4089+ // 'lazy' is a soft keyword, so it can still be used as a name.
4090+ assert_eq ! ( import_flags( "import foo as lazy\n " ) , [ plain] ) ;
4091+ assert_eq ! ( import_flags( "from lazy import foo\n " ) , [ plain] ) ;
4092+ }
4093+
40544094 fn extract_mypy_comments ( source : & str ) -> Vec < ( usize , String ) > {
40554095 let parsed = parse_unchecked ( source, ParseOptions :: from ( PySourceType :: Python ) ) ;
40564096 let line_index = LineIndex :: from_source_text ( source) ;
0 commit comments