Skip to main content

python_ast/codegen/
mod.rs

1//! Code generation for Python ASTs.
2
3use std::fmt::Debug;
4
5pub mod python_options;
6pub use python_options::*;
7
8/// Reexport the CodeGen from to_tokenstream
9pub use to_tokenstream::CodeGen;
10
11/// A type to track the context of code generation.
12#[derive(Clone, Debug)]
13pub enum CodeGenContext {
14    Module(String),
15    /// Inside a class body; the payload is the class name, so method
16    /// lowering can resolve `self` (receiver typing, `self.method()` calls)
17    /// against the class's definition in the symbol table.
18    Class(String),
19    Function,
20    Async(Box<CodeGenContext>),
21    /// Directly inside a loop body. `has_else` is true when the loop carries
22    /// a Python `else` clause, in which case `break` statements must also set
23    /// the `__rython_broke` flag the loop lowering declares.
24    Loop {
25        has_else: bool,
26        parent: Box<CodeGenContext>,
27    },
28    /// Inside a `try` block body, which lowers to a closure returning
29    /// `Result<(), PyException>`; `raise` (and failed `assert`) here lower
30    /// to `return Err(...)` so the except handlers can catch them.
31    TryBlock { parent: Box<CodeGenContext> },
32    /// Inside an `except` handler body, where the caught exception is in
33    /// scope as `__rython_exc`. `parent` is the context the try statement
34    /// itself appears in (handler code runs outside the try's closure).
35    ExceptHandler { parent: Box<CodeGenContext> },
36}
37
38impl CodeGenContext {
39    /// Whether code generated here runs inside a try-block closure, so a
40    /// raised exception can `return Err(...)` to be caught by its handlers.
41    pub fn in_try_block(&self) -> bool {
42        match self {
43            CodeGenContext::TryBlock { .. } => true,
44            CodeGenContext::ExceptHandler { parent }
45            | CodeGenContext::Loop { parent, .. } => parent.in_try_block(),
46            CodeGenContext::Async(inner) => inner.in_try_block(),
47            _ => false,
48        }
49    }
50
51    /// Whether a `break`/`continue` generated here would have to cross a
52    /// try-block closure boundary to reach its loop. Walking outward, the
53    /// first `Loop` means the statement binds to a real Rust loop; hitting
54    /// `TryBlock` first means the loop is outside the closure, so the
55    /// statement must be threaded out as a `PyFlow` signal instead.
56    pub fn break_crosses_try_closure(&self) -> bool {
57        match self {
58            CodeGenContext::Loop { .. } => false,
59            CodeGenContext::TryBlock { .. } => true,
60            CodeGenContext::ExceptHandler { parent } => parent.break_crosses_try_closure(),
61            CodeGenContext::Async(inner) => inner.break_crosses_try_closure(),
62            _ => false,
63        }
64    }
65
66    /// Whether the loop a `break` here targets carries a Python `else`
67    /// clause, so the break must also set the loop's `__rython_broke` flag.
68    pub fn break_target_has_else(&self) -> bool {
69        match self {
70            CodeGenContext::Loop { has_else, .. } => *has_else,
71            CodeGenContext::TryBlock { parent }
72            | CodeGenContext::ExceptHandler { parent } => parent.break_target_has_else(),
73            CodeGenContext::Async(inner) => inner.break_target_has_else(),
74            _ => false,
75        }
76    }
77
78    /// Whether code generated here runs inside an except handler, i.e. the
79    /// caught exception is in scope as `__rython_exc` (for bare `raise`).
80    pub fn in_except_handler(&self) -> bool {
81        match self {
82            CodeGenContext::ExceptHandler { .. } => true,
83            CodeGenContext::TryBlock { parent }
84            | CodeGenContext::Loop { parent, .. } => parent.in_except_handler(),
85            CodeGenContext::Async(inner) => inner.in_except_handler(),
86            _ => false,
87        }
88    }
89
90    /// The context for a nested function definition's body: exception scopes
91    /// don't cross function boundaries (a `raise` in a nested function can't
92    /// return out of the enclosing try's closure).
93    pub fn strip_exception_scopes(self) -> CodeGenContext {
94        match self {
95            CodeGenContext::TryBlock { parent }
96            | CodeGenContext::ExceptHandler { parent } => parent.strip_exception_scopes(),
97            other => other,
98        }
99    }
100
101    /// The name of the class whose method body this context sits inside, if
102    /// any — the class `self` refers to.
103    pub fn enclosing_class_name(&self) -> Option<&str> {
104        match self {
105            CodeGenContext::Class(name) => Some(name),
106            CodeGenContext::Async(inner) => inner.enclosing_class_name(),
107            CodeGenContext::Loop { parent, .. }
108            | CodeGenContext::TryBlock { parent }
109            | CodeGenContext::ExceptHandler { parent } => parent.enclosing_class_name(),
110            _ => None,
111        }
112    }
113}