◐ Shell
clean mode source ↗

Compiler crashes with panic instead of SyntaxError due to empty symbol table stack

I found a bug in the RustPython compiler.

Sometimes, for some reason, the internal symbol_table_stack becomes completely empty. Then the compiler tries to take an item out of that stack using .expect("compiler bug"). That means if the stack is empty, the whole RustPython process will crash. And that's exactly what happens.

I ran a specific test – the test is called assert_without_message_raises_class_directly. When that test runs, the compiler crashes and shows this error:

thread 'compile::tests::assert_without_message_raises_class_directly' (23444) panicked at crates\codegen\src\compile.rs:1016:39:
compiler bug
stack backtrace:
0: std::panicking::panic_handler
1: core::panicking::panic_fmt
2: core::panicking::panic_display
3: core::option::expect_failed

This is wrong. The compiler should never crash like this. Even if its internal state gets corrupted, it should log an error or show a proper Python-style error (like "Internal compiler error, please report"), but it should not kill the whole process.

My environment

Windows 11
Rust stable
Latest RustPython main branch
I reproduced this crash on my own machine.

What I expect

When this situation happens, the compiler should NOT panic. It should have a fallback – for example, create a dummy scope, print an error message, and continue. It should not stop.

Steps to reproduce

  1. Clone the RustPython repository.
  2. Run this command: cargo test -p rustpython-codegen -- --nocapture
  3. The test mentioned above will fail and show the panic.

Proposed fix

This way the compiler won't crash – it will just create a dummy scope and keep going. If there are any real errors, they will show up later.
In the file crates/codegen/src/compile.rs, around line 1016, there is this line:

let parent_scope = self.symbol_table_stack.pop().expect("compiler bug");

Change it to something like this:
let parent_scope = match self.symbol_table_stack.pop() {
    Some(scope) => scope,
    None => {
        log::error!("Compiler bug: popped empty symbol table stack. Creating dummy scope.");
        SymbolTable::new(ScopeType::Function, None)
    }
};