Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5442,8 +5442,6 @@ def run_in_child(cls, start_method):
flags = (tuple(sys.flags), grandchild_flags)
print(json.dumps(flags))

# TODO: RUSTPYTHON - SyntaxError in subprocess after fork
@unittest.expectedFailure
def test_flags(self):
import json
# start child process using unusual flags
Expand Down
135 changes: 135 additions & 0 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2366,6 +2366,14 @@ impl Compiler {
);

self.switch_to_block(after_block);
} else {
// Optimized-out asserts still need to consume any nested
// scope symbol tables they contain so later nested scopes
// stay aligned with AST traversal order.
self.consume_skipped_nested_scopes_in_expr(test)?;
if let Some(expr) = msg {
self.consume_skipped_nested_scopes_in_expr(expr)?;
}
}
}
ast::Stmt::Break(_) => {
Expand Down Expand Up @@ -7605,6 +7613,92 @@ impl Compiler {
})
}

fn consume_next_sub_table(&mut self) -> CompileResult<()> {
{
let _ = self.push_symbol_table()?;
}
let _ = self.pop_symbol_table();
Ok(())
}

fn consume_skipped_nested_scopes_in_expr(
&mut self,
expression: &ast::Expr,
) -> CompileResult<()> {
use ast::visitor::Visitor;

struct SkippedScopeVisitor<'a> {
compiler: &'a mut Compiler,
error: Option<CodegenError>,
}

impl SkippedScopeVisitor<'_> {
fn consume_scope(&mut self) {
if self.error.is_none() {
self.error = self.compiler.consume_next_sub_table().err();
}
}
}

impl ast::visitor::Visitor<'_> for SkippedScopeVisitor<'_> {
fn visit_expr(&mut self, expr: &ast::Expr) {
if self.error.is_some() {
return;
}

match expr {
ast::Expr::Lambda(ast::ExprLambda { parameters, .. }) => {
// Defaults are scanned before enter_scope in the
// symbol table builder, so their nested scopes
// precede the lambda scope in sub_tables.
if let Some(params) = parameters.as_deref() {
for default in params
.posonlyargs
.iter()
.chain(&params.args)
.chain(&params.kwonlyargs)
.filter_map(|p| p.default.as_deref())
{
self.visit_expr(default);
}
}
self.consume_scope();
}
ast::Expr::ListComp(ast::ExprListComp { generators, .. })
| ast::Expr::SetComp(ast::ExprSetComp { generators, .. })
| ast::Expr::Generator(ast::ExprGenerator { generators, .. }) => {
// leave_scope runs before the first iterator is
// scanned, so the comprehension scope comes first
// in sub_tables, then any nested scopes from the
// first iterator.
self.consume_scope();
if let Some(first) = generators.first() {
self.visit_expr(&first.iter);
}
}
ast::Expr::DictComp(ast::ExprDictComp { generators, .. }) => {
self.consume_scope();
if let Some(first) = generators.first() {
self.visit_expr(&first.iter);
}
}
_ => ast::visitor::walk_expr(self, expr),
}
}
}

let mut visitor = SkippedScopeVisitor {
compiler: self,
error: None,
};
visitor.visit_expr(expression);
if let Some(err) = visitor.error {
Err(err)
} else {
Ok(())
}
}

fn compile_comprehension(
&mut self,
name: &str,
Expand Down Expand Up @@ -9184,4 +9278,45 @@ async def test():
"
));
}

#[test]
fn test_optimized_assert_preserves_nested_scope_order() {
compile_exec_optimized(
"\
class S:
def f(self, sequence):
_formats = [self._types_mapping[type(item)] for item in sequence]
_list_len = len(_formats)
assert sum(len(fmt) <= 8 for fmt in _formats) == _list_len
_recreation_codes = [self._extract_recreation_code(item) for item in sequence]
",
);
}

#[test]
fn test_optimized_assert_with_nested_scope_in_first_iter() {
// First iterator of a comprehension is evaluated in the enclosing
// scope, so nested scopes inside it (the generator here) must also
// be consumed when the assert is optimized away.
compile_exec_optimized(
"\
def f(items):
assert [x for x in (y for y in items)]
return [x for x in items]
",
);
}

#[test]
fn test_optimized_assert_with_lambda_defaults() {
// Lambda default values are evaluated in the enclosing scope,
// so nested scopes inside defaults must be consumed.
compile_exec_optimized(
"\
def f(items):
assert (lambda x=[i for i in items]: x)()
return [x for x in items]
",
);
}
}
Loading