Skip to main content

oxilean_codegen/python_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::HashMap;
7
8use super::functions::*;
9use super::functions::{FromImports, PYTHON_KEYWORDS};
10
11/// A class variable (field) in a Python class.
12#[derive(Debug, Clone, PartialEq)]
13pub struct PythonClassVar {
14    /// Field name
15    pub name: String,
16    /// Type annotation
17    pub annotation: PythonType,
18    /// Optional default value
19    pub default: Option<PythonExpr>,
20}
21/// A Python function definition.
22#[derive(Debug, Clone, PartialEq)]
23pub struct PythonFunction {
24    /// Function name
25    pub name: String,
26    /// Parameters
27    pub params: Vec<PythonParam>,
28    /// Return type annotation
29    pub return_type: Option<PythonType>,
30    /// Function body
31    pub body: Vec<PythonStmt>,
32    /// Decorator expressions (e.g. `property`, `classmethod`, `staticmethod`)
33    pub decorators: Vec<String>,
34    /// Whether this is an async function
35    pub is_async: bool,
36    /// Whether this is a classmethod
37    pub is_classmethod: bool,
38    /// Whether this is a staticmethod
39    pub is_staticmethod: bool,
40}
41impl PythonFunction {
42    /// Create a new function with just a name, empty body.
43    pub fn new(name: impl Into<String>) -> Self {
44        PythonFunction {
45            name: name.into(),
46            params: Vec::new(),
47            return_type: None,
48            body: Vec::new(),
49            decorators: Vec::new(),
50            is_async: false,
51            is_classmethod: false,
52            is_staticmethod: false,
53        }
54    }
55}
56/// A Python class definition.
57#[derive(Debug, Clone, PartialEq)]
58pub struct PythonClass {
59    /// Class name
60    pub name: String,
61    /// Base classes
62    pub bases: Vec<String>,
63    /// Methods
64    pub methods: Vec<PythonFunction>,
65    /// Class variables / fields (for dataclasses)
66    pub class_vars: Vec<PythonClassVar>,
67    /// Whether to annotate with `@dataclass`
68    pub is_dataclass: bool,
69    /// Whether to annotate with `ABC` base or `@abstractmethod`
70    pub is_abstract: bool,
71    /// Additional decorators
72    pub decorators: Vec<String>,
73    /// Docstring
74    pub docstring: Option<String>,
75}
76impl PythonClass {
77    /// Create a new empty class.
78    pub fn new(name: impl Into<String>) -> Self {
79        PythonClass {
80            name: name.into(),
81            bases: Vec::new(),
82            methods: Vec::new(),
83            class_vars: Vec::new(),
84            is_dataclass: false,
85            is_abstract: false,
86            decorators: Vec::new(),
87            docstring: None,
88        }
89    }
90}
91/// Python function parameter with optional type annotation and default.
92#[derive(Debug, Clone, PartialEq)]
93pub struct PythonParam {
94    /// Parameter name
95    pub name: String,
96    /// Optional type annotation
97    pub annotation: Option<PythonType>,
98    /// Optional default value
99    pub default: Option<PythonExpr>,
100    /// Whether this is a `*args` parameter
101    pub is_vararg: bool,
102    /// Whether this is a `**kwargs` parameter
103    pub is_kwarg: bool,
104    /// Whether this is a keyword-only parameter (after `*`)
105    pub is_keyword_only: bool,
106}
107impl PythonParam {
108    /// Create a simple parameter with just a name.
109    pub fn simple(name: impl Into<String>) -> Self {
110        PythonParam {
111            name: name.into(),
112            annotation: None,
113            default: None,
114            is_vararg: false,
115            is_kwarg: false,
116            is_keyword_only: false,
117        }
118    }
119    /// Create a parameter with a type annotation.
120    pub fn typed(name: impl Into<String>, ty: PythonType) -> Self {
121        PythonParam {
122            name: name.into(),
123            annotation: Some(ty),
124            default: None,
125            is_vararg: false,
126            is_kwarg: false,
127            is_keyword_only: false,
128        }
129    }
130}
131/// A match case arm: `case <pattern>: <body>`
132#[derive(Debug, Clone, PartialEq)]
133pub struct MatchArm {
134    /// The pattern (as raw Python text for flexibility)
135    pub pattern: String,
136    /// Optional guard: `if guard`
137    pub guard: Option<PythonExpr>,
138    /// Body statements
139    pub body: Vec<PythonStmt>,
140}
141/// Python literal value.
142#[derive(Debug, Clone, PartialEq)]
143pub enum PythonLit {
144    /// Integer literal: `42`, `-7`
145    Int(i64),
146    /// Float literal: `3.14`, `-0.5`
147    Float(f64),
148    /// String literal: `"hello"` or `'hello'`
149    Str(String),
150    /// Boolean literal: `True` or `False`
151    Bool(bool),
152    /// `None` literal
153    None,
154    /// Bytes literal: `b"data"`
155    Bytes(Vec<u8>),
156    /// Ellipsis: `...`
157    Ellipsis,
158}
159/// Python code generation backend.
160///
161/// Compiles LCNF function declarations to a `PythonModule` containing
162/// Python 3.10+ code with type hints.
163pub struct PythonBackend {
164    /// The module being built.
165    pub module: PythonModule,
166    /// Mapping from LCNF names to mangled Python names.
167    pub fn_map: HashMap<String, String>,
168    /// Counter for generating fresh temporary variable names.
169    pub fresh_counter: usize,
170}
171impl PythonBackend {
172    /// Create a new Python backend.
173    pub fn new() -> Self {
174        PythonBackend {
175            module: PythonModule::new(),
176            fn_map: HashMap::new(),
177            fresh_counter: 0,
178        }
179    }
180    /// Generate a fresh temporary variable name: `_t0`, `_t1`, etc.
181    pub fn fresh_var(&mut self) -> String {
182        let n = self.fresh_counter;
183        self.fresh_counter += 1;
184        format!("_t{}", n)
185    }
186    /// Mangle an LCNF name into a valid Python identifier.
187    ///
188    /// Rules:
189    /// - Replace `.` with `_`
190    /// - Replace `'` (prime) with `_prime`
191    /// - Replace `-` with `_`
192    /// - Prefix Python reserved words with `_`
193    pub fn mangle_name(&self, name: &str) -> String {
194        let mangled: String = name
195            .chars()
196            .map(|c| match c {
197                '.' | '\'' | '-' | ' ' => '_',
198                c if c.is_alphanumeric() || c == '_' => c,
199                _ => '_',
200            })
201            .collect();
202        if PYTHON_KEYWORDS.contains(&mangled.as_str())
203            || mangled.starts_with(|c: char| c.is_ascii_digit())
204        {
205            format!("_{}", mangled)
206        } else if mangled.is_empty() {
207            "_anon".to_string()
208        } else {
209            mangled
210        }
211    }
212    /// Top-level entry point: compile a slice of LCNF function declarations
213    /// into a Python module string.
214    pub fn compile_module(decls: &[LcnfFunDecl]) -> Result<String, String> {
215        let mut backend = PythonBackend::new();
216        backend.module.add_from_import(
217            "typing",
218            vec![
219                ("Any".to_string(), None),
220                ("Optional".to_string(), None),
221                ("Union".to_string(), None),
222                ("List".to_string(), None),
223                ("Dict".to_string(), None),
224                ("Tuple".to_string(), None),
225                ("Callable".to_string(), None),
226            ],
227        );
228        for decl in decls {
229            let py_name = backend.mangle_name(&decl.name);
230            backend.fn_map.insert(decl.name.clone(), py_name);
231        }
232        for decl in decls {
233            let func = backend.compile_decl(decl)?;
234            backend.module.add_function(func);
235        }
236        for decl in decls {
237            if let Some(py_name) = backend.fn_map.get(&decl.name) {
238                backend.module.all_exports.push(py_name.clone());
239            }
240        }
241        Ok(backend.module.emit())
242    }
243    /// Compile a single LCNF function declaration into a `PythonFunction`.
244    pub fn compile_decl(&mut self, decl: &LcnfFunDecl) -> Result<PythonFunction, String> {
245        let py_name = self.mangle_name(&decl.name);
246        let mut func = PythonFunction::new(py_name);
247        for param in &decl.params {
248            let param_name = format!("_v{}", param.id.0);
249            func.params.push(PythonParam {
250                name: param_name,
251                annotation: Some(PythonType::Any),
252                default: None,
253                is_vararg: false,
254                is_kwarg: false,
255                is_keyword_only: false,
256            });
257        }
258        func.return_type = Some(PythonType::Any);
259        let body_stmts = self.compile_expr_to_stmts(&decl.body)?;
260        func.body = body_stmts;
261        Ok(func)
262    }
263    /// Compile an LCNF expression into a list of Python statements,
264    /// ending with a `return`.
265    pub(super) fn compile_expr_to_stmts(
266        &mut self,
267        expr: &LcnfExpr,
268    ) -> Result<Vec<PythonStmt>, String> {
269        self.compile_expr_stmts_inner(expr, &mut Vec::new())
270    }
271    /// Compile an LCNF expression into a sequence of Python statements.
272    pub(super) fn compile_expr_stmts_inner(
273        &mut self,
274        expr: &LcnfExpr,
275        stmts: &mut Vec<PythonStmt>,
276    ) -> Result<Vec<PythonStmt>, String> {
277        match expr {
278            LcnfExpr::Let {
279                name, value, body, ..
280            } => {
281                let py_name = self.mangle_name(name);
282                let py_val = self.compile_let_value(value)?;
283                stmts.push(PythonStmt::Assign(vec![PythonExpr::Var(py_name)], py_val));
284                self.compile_expr_stmts_inner(body, stmts)
285            }
286            LcnfExpr::Return(arg) => {
287                let py_expr = self.compile_arg(arg);
288                stmts.push(PythonStmt::Return(Some(py_expr)));
289                Ok(stmts.clone())
290            }
291            LcnfExpr::Unreachable => {
292                stmts.push(PythonStmt::Raise(Some(PythonExpr::Call(
293                    Box::new(PythonExpr::Var("RuntimeError".to_string())),
294                    vec![PythonExpr::Lit(PythonLit::Str("unreachable".to_string()))],
295                    vec![],
296                ))));
297                Ok(stmts.clone())
298            }
299            LcnfExpr::TailCall(func, args) => {
300                let py_func = self.compile_arg(func);
301                let py_args: Vec<PythonExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
302                stmts.push(PythonStmt::Return(Some(PythonExpr::Call(
303                    Box::new(py_func),
304                    py_args,
305                    vec![],
306                ))));
307                Ok(stmts.clone())
308            }
309            LcnfExpr::Case {
310                scrutinee,
311                alts,
312                default,
313                ..
314            } => {
315                let scrutinee_name = format!("_v{}", scrutinee.0);
316                let match_arms: Vec<MatchArm> = alts
317                    .iter()
318                    .map(|alt| {
319                        let pattern = if alt.params.is_empty() {
320                            alt.ctor_name.clone()
321                        } else {
322                            let param_names: Vec<String> =
323                                alt.params.iter().map(|p| format!("_v{}", p.id.0)).collect();
324                            format!("{}({})", alt.ctor_name, param_names.join(", "))
325                        };
326                        let mut arm_stmts = Vec::new();
327                        let _ = self.compile_expr_stmts_inner(&alt.body, &mut arm_stmts);
328                        MatchArm {
329                            pattern,
330                            guard: None,
331                            body: arm_stmts,
332                        }
333                    })
334                    .collect();
335                let mut all_arms = match_arms;
336                if let Some(def) = default {
337                    let mut def_stmts = Vec::new();
338                    let _ = self.compile_expr_stmts_inner(def, &mut def_stmts);
339                    all_arms.push(MatchArm {
340                        pattern: "_".to_string(),
341                        guard: None,
342                        body: def_stmts,
343                    });
344                }
345                stmts.push(PythonStmt::Match(PythonExpr::Var(scrutinee_name), all_arms));
346                Ok(stmts.clone())
347            }
348        }
349    }
350    /// Compile an LCNF argument (atomic value) into a Python expression.
351    pub(super) fn compile_arg(&self, arg: &LcnfArg) -> PythonExpr {
352        match arg {
353            LcnfArg::Var(id) => PythonExpr::Var(format!("_v{}", id.0)),
354            LcnfArg::Lit(lit) => self.compile_lit(lit),
355            LcnfArg::Erased => PythonExpr::Lit(PythonLit::None),
356            LcnfArg::Type(_) => PythonExpr::Lit(PythonLit::None),
357        }
358    }
359    /// Compile an LCNF let-bound value into a Python expression.
360    pub(super) fn compile_let_value(&mut self, value: &LcnfLetValue) -> Result<PythonExpr, String> {
361        match value {
362            LcnfLetValue::App(func, args) => {
363                let py_func = self.compile_arg(func);
364                let py_args: Vec<PythonExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
365                Ok(PythonExpr::Call(Box::new(py_func), py_args, vec![]))
366            }
367            LcnfLetValue::Proj(_, idx, var) => {
368                let obj = PythonExpr::Var(format!("_v{}", var.0));
369                Ok(PythonExpr::Subscript(
370                    Box::new(obj),
371                    Box::new(PythonExpr::Lit(PythonLit::Int(*idx as i64))),
372                ))
373            }
374            LcnfLetValue::Ctor(name, _, args) => {
375                let py_args: Vec<PythonExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
376                Ok(PythonExpr::Call(
377                    Box::new(PythonExpr::Var(self.mangle_name(name))),
378                    py_args,
379                    vec![],
380                ))
381            }
382            LcnfLetValue::Lit(lit) => Ok(self.compile_lit(lit)),
383            LcnfLetValue::Erased => Ok(PythonExpr::Lit(PythonLit::None)),
384            LcnfLetValue::FVar(id) => Ok(PythonExpr::Var(format!("_v{}", id.0))),
385            LcnfLetValue::Reset(id) => Ok(PythonExpr::Var(format!("_v{}", id.0))),
386            LcnfLetValue::Reuse(id, name, _, args) => {
387                let py_args: Vec<PythonExpr> = args.iter().map(|a| self.compile_arg(a)).collect();
388                let _ = id;
389                Ok(PythonExpr::Call(
390                    Box::new(PythonExpr::Var(self.mangle_name(name))),
391                    py_args,
392                    vec![],
393                ))
394            }
395        }
396    }
397    /// Compile an LCNF literal into a Python expression.
398    pub(super) fn compile_lit(&self, lit: &LcnfLit) -> PythonExpr {
399        match lit {
400            LcnfLit::Nat(n) => PythonExpr::Lit(PythonLit::Int(*n as i64)),
401            LcnfLit::Str(s) => PythonExpr::Lit(PythonLit::Str(s.clone())),
402        }
403    }
404    /// Emit the compiled module as a Python string.
405    pub fn emit_module(&self) -> String {
406        self.module.emit()
407    }
408}
409/// Python type annotation for type-hinted code generation (PEP 484).
410#[derive(Debug, Clone, PartialEq, Eq, Hash)]
411pub enum PythonType {
412    /// `int`
413    Int,
414    /// `float`
415    Float,
416    /// `str`
417    Str,
418    /// `bool`
419    Bool,
420    /// `None`
421    None_,
422    /// `list[T]`
423    List(Box<PythonType>),
424    /// `dict[K, V]`
425    Dict(Box<PythonType>, Box<PythonType>),
426    /// `tuple[T1, T2, ...]`
427    Tuple(Vec<PythonType>),
428    /// `T | None` (Optional\[T\])
429    Optional(Box<PythonType>),
430    /// `T1 | T2 | ...` (Union)
431    Union(Vec<PythonType>),
432    /// User-defined type / class name
433    Custom(String),
434    /// `Any` (typing.Any)
435    Any,
436    /// `Callable[[A, B], R]`
437    Callable,
438    /// `set[T]`
439    Set(Box<PythonType>),
440    /// `frozenset[T]`
441    FrozenSet(Box<PythonType>),
442    /// `Generator[Y, S, R]`
443    Generator(Box<PythonType>, Box<PythonType>, Box<PythonType>),
444    /// `AsyncGenerator[Y, S]`
445    AsyncGenerator(Box<PythonType>, Box<PythonType>),
446    /// `Iterator[T]`
447    Iterator(Box<PythonType>),
448    /// `Iterable[T]`
449    Iterable(Box<PythonType>),
450    /// `Sequence[T]`
451    Sequence(Box<PythonType>),
452    /// `Mapping[K, V]`
453    Mapping(Box<PythonType>, Box<PythonType>),
454    /// `ClassVar[T]`
455    ClassVar(Box<PythonType>),
456    /// `Final[T]`
457    Final(Box<PythonType>),
458    /// `type[T]` (the class itself)
459    Type(Box<PythonType>),
460}
461/// Part of an f-string.
462#[derive(Debug, Clone, PartialEq)]
463pub enum FStringPart {
464    /// Raw string text
465    Literal(String),
466    /// Interpolated expression: `{expr}`
467    Expr(PythonExpr),
468    /// Interpolated expression with format spec: `{expr:.2f}`
469    ExprWithFormat(PythonExpr, String),
470}
471/// Python statement for code generation.
472#[derive(Debug, Clone, PartialEq)]
473pub enum PythonStmt {
474    /// Expression statement: `expr`
475    Expr(PythonExpr),
476    /// Simple assignment: `target = value`
477    Assign(Vec<PythonExpr>, PythonExpr),
478    /// Augmented assignment: `target op= value`
479    AugAssign(PythonExpr, String, PythonExpr),
480    /// Annotated assignment: `target: type = value`
481    AnnAssign(String, PythonType, Option<PythonExpr>),
482    /// If/elif/else statement
483    If(
484        PythonExpr,
485        Vec<PythonStmt>,
486        Vec<(PythonExpr, Vec<PythonStmt>)>,
487        Vec<PythonStmt>,
488    ),
489    /// For loop: `for var in iter: body`
490    For(String, PythonExpr, Vec<PythonStmt>, Vec<PythonStmt>),
491    /// While loop: `while cond: body`
492    While(PythonExpr, Vec<PythonStmt>, Vec<PythonStmt>),
493    /// With statement: `with expr as var: body`
494    With(Vec<(PythonExpr, Option<String>)>, Vec<PythonStmt>),
495    /// Try/except/else/finally statement
496    Try(
497        Vec<PythonStmt>,
498        Vec<(Option<PythonExpr>, Option<String>, Vec<PythonStmt>)>,
499        Vec<PythonStmt>,
500        Vec<PythonStmt>,
501    ),
502    /// Return statement: `return expr`
503    Return(Option<PythonExpr>),
504    /// Raise statement: `raise expr`
505    Raise(Option<PythonExpr>),
506    /// Delete statement: `del name`
507    Del(Vec<PythonExpr>),
508    /// Pass statement: `pass`
509    Pass,
510    /// Break statement: `break`
511    Break,
512    /// Continue statement: `continue`
513    Continue,
514    /// Import statement: `import module` or `import module as alias`
515    Import(Vec<(String, Option<String>)>),
516    /// From-import statement: `from module import name` or `from module import *`
517    From(String, Vec<(String, Option<String>)>),
518    /// Class definition
519    ClassDef(PythonClass),
520    /// Function definition
521    FuncDef(PythonFunction),
522    /// Async function definition
523    AsyncFuncDef(PythonFunction),
524    /// Docstring statement
525    Docstring(String),
526    /// Assert statement: `assert expr, msg`
527    Assert(PythonExpr, Option<PythonExpr>),
528    /// Global declaration: `global x, y`
529    Global(Vec<String>),
530    /// Nonlocal declaration: `nonlocal x, y`
531    Nonlocal(Vec<String>),
532    /// Match statement (Python 3.10+)
533    Match(PythonExpr, Vec<MatchArm>),
534    /// Raw Python text (escape hatch)
535    Raw(String),
536}
537/// A complete Python module (one `.py` file).
538#[derive(Debug, Clone)]
539pub struct PythonModule {
540    /// Module-level imports: `import X` or `import X as Y`
541    pub imports: Vec<(String, Option<String>)>,
542    /// From-imports: `from X import Y` or `from X import Y as Z`
543    pub from_imports: FromImports,
544    /// Top-level class definitions
545    pub classes: Vec<PythonClass>,
546    /// Top-level function definitions
547    pub functions: Vec<PythonFunction>,
548    /// Other top-level statements
549    pub statements: Vec<PythonStmt>,
550    /// Module docstring
551    pub module_docstring: Option<String>,
552    /// `__all__` exports
553    pub all_exports: Vec<String>,
554}
555impl PythonModule {
556    /// Create a new empty Python module.
557    pub fn new() -> Self {
558        PythonModule {
559            imports: Vec::new(),
560            from_imports: Vec::new(),
561            classes: Vec::new(),
562            functions: Vec::new(),
563            statements: Vec::new(),
564            module_docstring: None,
565            all_exports: Vec::new(),
566        }
567    }
568    /// Add a module-level import.
569    pub fn add_import(&mut self, module: impl Into<String>, alias: Option<String>) {
570        self.imports.push((module.into(), alias));
571    }
572    /// Add a from-import.
573    pub fn add_from_import(
574        &mut self,
575        module: impl Into<String>,
576        names: Vec<(String, Option<String>)>,
577    ) {
578        self.from_imports.push((module.into(), names));
579    }
580    /// Add a class definition.
581    pub fn add_class(&mut self, cls: PythonClass) {
582        self.classes.push(cls);
583    }
584    /// Add a function definition.
585    pub fn add_function(&mut self, func: PythonFunction) {
586        self.functions.push(func);
587    }
588    /// Add a top-level statement.
589    pub fn add_statement(&mut self, stmt: PythonStmt) {
590        self.statements.push(stmt);
591    }
592    /// Emit the full Python module as a string.
593    pub fn emit(&self) -> String {
594        let mut out = String::new();
595        if let Some(doc) = &self.module_docstring {
596            out.push_str(&format!("\"\"\"{}\"\"\"\n\n", doc));
597        }
598        let future_imports: Vec<_> = self
599            .from_imports
600            .iter()
601            .filter(|(m, _)| m == "__future__")
602            .collect();
603        for (module, names) in &future_imports {
604            out.push_str(&format_from_import(module, names));
605            out.push('\n');
606        }
607        if !future_imports.is_empty() {
608            out.push('\n');
609        }
610        for (module, alias) in &self.imports {
611            match alias {
612                Some(a) => out.push_str(&format!("import {} as {}\n", module, a)),
613                None => out.push_str(&format!("import {}\n", module)),
614            }
615        }
616        if !self.imports.is_empty() {
617            out.push('\n');
618        }
619        let non_future: Vec<_> = self
620            .from_imports
621            .iter()
622            .filter(|(m, _)| m != "__future__")
623            .collect();
624        for (module, names) in &non_future {
625            out.push_str(&format_from_import(module, names));
626            out.push('\n');
627        }
628        if !non_future.is_empty() {
629            out.push('\n');
630        }
631        if !self.all_exports.is_empty() {
632            out.push_str("__all__ = [");
633            for (i, name) in self.all_exports.iter().enumerate() {
634                if i > 0 {
635                    out.push_str(", ");
636                }
637                out.push_str(&format!("\"{}\"", name));
638            }
639            out.push_str("]\n\n");
640        }
641        for cls in &self.classes {
642            out.push_str(&emit_class(cls, 0));
643            out.push_str("\n\n");
644        }
645        for func in &self.functions {
646            out.push_str(&emit_function(func, 0));
647            out.push_str("\n\n");
648        }
649        for stmt in &self.statements {
650            out.push_str(&emit_stmt(stmt, 0));
651            out.push('\n');
652        }
653        out
654    }
655}
656/// Python expression for code generation.
657#[derive(Debug, Clone, PartialEq)]
658pub enum PythonExpr {
659    /// A literal value: `42`, `"hello"`, `True`, `None`, etc.
660    Lit(PythonLit),
661    /// A variable identifier: `x`, `my_var`
662    Var(String),
663    /// Binary operator: `lhs + rhs`, `a == b`, etc.
664    BinOp(String, Box<PythonExpr>, Box<PythonExpr>),
665    /// Unary operator: `-x`, `not x`, `~x`
666    UnaryOp(String, Box<PythonExpr>),
667    /// Function call: `f(a, b, key=val)`
668    Call(Box<PythonExpr>, Vec<PythonExpr>, Vec<(String, PythonExpr)>),
669    /// Attribute access: `obj.field`
670    Attr(Box<PythonExpr>, String),
671    /// Subscript: `obj[idx]`
672    Subscript(Box<PythonExpr>, Box<PythonExpr>),
673    /// Lambda expression: `lambda x, y: x + y`
674    Lambda(Vec<String>, Box<PythonExpr>),
675    /// Conditional (ternary) expression: `a if cond else b`
676    IfExpr(Box<PythonExpr>, Box<PythonExpr>, Box<PythonExpr>),
677    /// List comprehension: `[expr for var in iter if cond]`
678    ListComp(
679        Box<PythonExpr>,
680        String,
681        Box<PythonExpr>,
682        Option<Box<PythonExpr>>,
683    ),
684    /// Dict comprehension: `{k: v for k, v in items}`
685    DictComp(
686        Box<PythonExpr>,
687        Box<PythonExpr>,
688        String,
689        String,
690        Box<PythonExpr>,
691    ),
692    /// Set comprehension: `{x for x in iter}`
693    SetComp(
694        Box<PythonExpr>,
695        String,
696        Box<PythonExpr>,
697        Option<Box<PythonExpr>>,
698    ),
699    /// Generator expression: `(expr for var in iter if cond)`
700    GenExpr(
701        Box<PythonExpr>,
702        String,
703        Box<PythonExpr>,
704        Option<Box<PythonExpr>>,
705    ),
706    /// Tuple literal: `(a, b, c)` or `a, b, c`
707    Tuple(Vec<PythonExpr>),
708    /// List literal: `[a, b, c]`
709    List(Vec<PythonExpr>),
710    /// Dict literal: `{"key": val, ...}`
711    Dict(Vec<(PythonExpr, PythonExpr)>),
712    /// Set literal: `{a, b, c}`
713    Set(Vec<PythonExpr>),
714    /// Await expression: `await expr`
715    Await(Box<PythonExpr>),
716    /// Yield expression: `yield expr`
717    Yield(Option<Box<PythonExpr>>),
718    /// Yield from expression: `yield from expr`
719    YieldFrom(Box<PythonExpr>),
720    /// Match expression (Python 3.10+): used as target in match stmt
721    Match(Box<PythonExpr>),
722    /// f-string: `f"hello {name}!"`
723    FString(Vec<FStringPart>),
724    /// Walrus operator (assignment expression): `name := expr`
725    Walrus(String, Box<PythonExpr>),
726    /// Star expression: `*args`
727    Star(Box<PythonExpr>),
728    /// Double-star expression: `**kwargs`
729    DoubleStar(Box<PythonExpr>),
730    /// Slice: `a:b:c`
731    Slice(
732        Option<Box<PythonExpr>>,
733        Option<Box<PythonExpr>>,
734        Option<Box<PythonExpr>>,
735    ),
736}