@@ -2366,6 +2366,14 @@ impl Compiler {
23662366 ) ;
23672367
23682368 self . switch_to_block ( after_block) ;
2369+ } else {
2370+ // Optimized-out asserts still need to consume any nested
2371+ // scope symbol tables they contain so later nested scopes
2372+ // stay aligned with AST traversal order.
2373+ self . consume_skipped_nested_scopes_in_expr ( test) ?;
2374+ if let Some ( expr) = msg {
2375+ self . consume_skipped_nested_scopes_in_expr ( expr) ?;
2376+ }
23692377 }
23702378 }
23712379 ast:: Stmt :: Break ( _) => {
@@ -7605,6 +7613,92 @@ impl Compiler {
76057613 } )
76067614 }
76077615
7616+ fn consume_next_sub_table ( & mut self ) -> CompileResult < ( ) > {
7617+ {
7618+ let _ = self . push_symbol_table ( ) ?;
7619+ }
7620+ let _ = self . pop_symbol_table ( ) ;
7621+ Ok ( ( ) )
7622+ }
7623+
7624+ fn consume_skipped_nested_scopes_in_expr (
7625+ & mut self ,
7626+ expression : & ast:: Expr ,
7627+ ) -> CompileResult < ( ) > {
7628+ use ast:: visitor:: Visitor ;
7629+
7630+ struct SkippedScopeVisitor < ' a > {
7631+ compiler : & ' a mut Compiler ,
7632+ error : Option < CodegenError > ,
7633+ }
7634+
7635+ impl SkippedScopeVisitor < ' _ > {
7636+ fn consume_scope ( & mut self ) {
7637+ if self . error . is_none ( ) {
7638+ self . error = self . compiler . consume_next_sub_table ( ) . err ( ) ;
7639+ }
7640+ }
7641+ }
7642+
7643+ impl ast:: visitor:: Visitor < ' _ > for SkippedScopeVisitor < ' _ > {
7644+ fn visit_expr ( & mut self , expr : & ast:: Expr ) {
7645+ if self . error . is_some ( ) {
7646+ return ;
7647+ }
7648+
7649+ match expr {
7650+ ast:: Expr :: Lambda ( ast:: ExprLambda { parameters, .. } ) => {
7651+ // Defaults are scanned before enter_scope in the
7652+ // symbol table builder, so their nested scopes
7653+ // precede the lambda scope in sub_tables.
7654+ if let Some ( params) = parameters. as_deref ( ) {
7655+ for default in params
7656+ . posonlyargs
7657+ . iter ( )
7658+ . chain ( & params. args )
7659+ . chain ( & params. kwonlyargs )
7660+ . filter_map ( |p| p. default . as_deref ( ) )
7661+ {
7662+ self . visit_expr ( default) ;
7663+ }
7664+ }
7665+ self . consume_scope ( ) ;
7666+ }
7667+ ast:: Expr :: ListComp ( ast:: ExprListComp { generators, .. } )
7668+ | ast:: Expr :: SetComp ( ast:: ExprSetComp { generators, .. } )
7669+ | ast:: Expr :: Generator ( ast:: ExprGenerator { generators, .. } ) => {
7670+ // leave_scope runs before the first iterator is
7671+ // scanned, so the comprehension scope comes first
7672+ // in sub_tables, then any nested scopes from the
7673+ // first iterator.
7674+ self . consume_scope ( ) ;
7675+ if let Some ( first) = generators. first ( ) {
7676+ self . visit_expr ( & first. iter ) ;
7677+ }
7678+ }
7679+ ast:: Expr :: DictComp ( ast:: ExprDictComp { generators, .. } ) => {
7680+ self . consume_scope ( ) ;
7681+ if let Some ( first) = generators. first ( ) {
7682+ self . visit_expr ( & first. iter ) ;
7683+ }
7684+ }
7685+ _ => ast:: visitor:: walk_expr ( self , expr) ,
7686+ }
7687+ }
7688+ }
7689+
7690+ let mut visitor = SkippedScopeVisitor {
7691+ compiler : self ,
7692+ error : None ,
7693+ } ;
7694+ visitor. visit_expr ( expression) ;
7695+ if let Some ( err) = visitor. error {
7696+ Err ( err)
7697+ } else {
7698+ Ok ( ( ) )
7699+ }
7700+ }
7701+
76087702 fn compile_comprehension (
76097703 & mut self ,
76107704 name : & str ,
@@ -9184,4 +9278,45 @@ async def test():
91849278"
91859279 ) ) ;
91869280 }
9281+
9282+ #[ test]
9283+ fn test_optimized_assert_preserves_nested_scope_order ( ) {
9284+ compile_exec_optimized (
9285+ "\
9286+ class S:
9287+ def f(self, sequence):
9288+ _formats = [self._types_mapping[type(item)] for item in sequence]
9289+ _list_len = len(_formats)
9290+ assert sum(len(fmt) <= 8 for fmt in _formats) == _list_len
9291+ _recreation_codes = [self._extract_recreation_code(item) for item in sequence]
9292+ " ,
9293+ ) ;
9294+ }
9295+
9296+ #[ test]
9297+ fn test_optimized_assert_with_nested_scope_in_first_iter ( ) {
9298+ // First iterator of a comprehension is evaluated in the enclosing
9299+ // scope, so nested scopes inside it (the generator here) must also
9300+ // be consumed when the assert is optimized away.
9301+ compile_exec_optimized (
9302+ "\
9303+ def f(items):
9304+ assert [x for x in (y for y in items)]
9305+ return [x for x in items]
9306+ " ,
9307+ ) ;
9308+ }
9309+
9310+ #[ test]
9311+ fn test_optimized_assert_with_lambda_defaults ( ) {
9312+ // Lambda default values are evaluated in the enclosing scope,
9313+ // so nested scopes inside defaults must be consumed.
9314+ compile_exec_optimized (
9315+ "\
9316+ def f(items):
9317+ assert (lambda x=[i for i in items]: x)()
9318+ return [x for x in items]
9319+ " ,
9320+ ) ;
9321+ }
91879322}
0 commit comments