Skip to main content

python_ast/ast/tree/
expression.rs

1use proc_macro2::TokenStream;
2use pyo3::{Borrowed, Bound, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods, types::PyTypeMethods};
3use quote::quote;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    dump, err_from, extraction_failure, Attribute, Await, BinOp, BoolOp, Call, CodeGen, CodeGenContext, Compare,
8    Constant, Dict, DictComp, ExprTypeNotYetImplemented, FormattedValue, GeneratorExp, IfExp,
9    JoinedStr, Lambda, ListComp, Name, NamedExpr, Node, PythonOptions, Set, SetComp, Starred,
10    Subscript, SymbolTableScopes, Tuple, UnaryOp, Yield, YieldFrom,
11};
12
13/// Mostly this shouldn't be used, but it exists so that we don't have to manually implement FromPyObject on all of ExprType
14#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
15#[repr(transparent)]
16pub struct Container<T>(pub T);
17
18impl<'a, 'py> FromPyObject<'a, 'py> for Container<crate::pytypes::List<ExprType>> {
19    type Error = pyo3::PyErr;
20    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
21        let list = crate::pytypes::List::<ExprType>::new();
22
23        tracing::debug!("pylist: {}", dump(&ob, Some(4))?);
24        let _converted_list: Vec<Bound<PyAny>> = ob.extract()?;
25        for item in _converted_list.iter() {
26            tracing::debug!("item: {:?}", item);
27        }
28
29        Ok(Self(list))
30    }
31}
32
33#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
34pub enum ExprType {
35    BoolOp(BoolOp),
36    NamedExpr(NamedExpr),
37    BinOp(BinOp),
38    UnaryOp(UnaryOp),
39    Lambda(Lambda),
40    IfExp(IfExp),
41    Dict(Dict),
42    Set(Set),
43    ListComp(ListComp),
44    DictComp(DictComp),
45    SetComp(SetComp),
46    GeneratorExp(GeneratorExp),
47    Await(Await),
48    Yield(Yield),
49    YieldFrom(YieldFrom),
50    Compare(Compare),
51    Call(Call),
52    FormattedValue(FormattedValue),
53    JoinedStr(JoinedStr),
54    Constant(Constant),
55
56    /// These can appear in a few places, such as the left side of an assignment.
57    Attribute(Attribute),
58    Subscript(Subscript),
59    Starred(Starred),
60    Name(Name),
61    List(Vec<ExprType>),
62    Tuple(Tuple),
63    /*Slice(),*/
64    NoneType(Constant),
65
66    Unimplemented(String),
67    #[default]
68    Unknown,
69}
70
71impl<'a, 'py> FromPyObject<'a, 'py> for ExprType {
72    type Error = pyo3::PyErr;
73    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
74        tracing::debug!("exprtype ob: {}", dump(&ob, Some(4))?);
75
76        let expr_type = ob
77            .get_type()
78            .name()
79            .map_err(|e| extraction_failure("expression type name", &ob, e))?;
80        tracing::debug!("expression type: {}, value: {}", expr_type, dump(&ob, None)?);
81
82        let r = match expr_type.extract::<String>()?.as_str() {
83            "Attribute" => {
84                let a = ob.extract().map_err(|e| extraction_failure("extracting Attribute in expression", &ob, e))?;
85                Ok(Self::Attribute(a))
86            }
87            "Await" => {
88                //println!("await: {}", dump(&ob, None)?);
89                let a = ob.extract().map_err(|e| extraction_failure("extracting await value in expression", &ob, e))?;
90                Ok(Self::Await(a))
91            }
92            "BoolOp" => {
93                let b = ob.extract().map_err(|e| extraction_failure("extracting BoolOp in expression", &ob, e))?;
94                Ok(Self::BoolOp(b))
95            }
96            "Call" => {
97                let et = ob.extract().map_err(|e| extraction_failure("parsing Call expression", &ob, e))?;
98                Ok(Self::Call(et))
99            }
100            "Compare" => {
101                let c = ob.extract().map_err(|e| extraction_failure("extracting Compare in expression", &ob, e))?;
102                Ok(Self::Compare(c))
103            }
104            "Constant" => {
105                tracing::debug!("constant: {}", dump(&ob, None)?);
106                let c = ob.extract().map_err(|e| extraction_failure("extracting Constant in expression", &ob, e))?;
107                Ok(Self::Constant(c))
108            }
109            "List" => {
110                // Extract the list elements using the 'elts' attribute
111                let elts_attr = ob
112                    .getattr("elts")
113                    .map_err(|e| extraction_failure("list elements", &ob, e))?;
114                let elts_vec: Vec<Bound<PyAny>> = elts_attr
115                    .extract()
116                    .map_err(|e| extraction_failure("list elements", &ob, e))?;
117
118                // Convert each element to ExprType
119                let mut expr_list = Vec::new();
120                for elt in elts_vec {
121                    let expr: ExprType = elt
122                        .extract()
123                        .map_err(|e| extraction_failure("list element", &elt, e))?;
124                    expr_list.push(expr);
125                }
126                
127                Ok(Self::List(expr_list))
128            }
129            "ListComp" => {
130                let lc = ob.extract().map_err(|e| extraction_failure("extracting ListComp in expression", &ob, e))?;
131                Ok(Self::ListComp(lc))
132            }
133            "DictComp" => {
134                let dc = ob.extract().map_err(|e| extraction_failure("extracting DictComp in expression", &ob, e))?;
135                Ok(Self::DictComp(dc))
136            }
137            "SetComp" => {
138                let sc = ob.extract().map_err(|e| extraction_failure("extracting SetComp in expression", &ob, e))?;
139                Ok(Self::SetComp(sc))
140            }
141            "GeneratorExp" => {
142                let ge = ob.extract().map_err(|e| extraction_failure("extracting GeneratorExp in expression", &ob, e))?;
143                Ok(Self::GeneratorExp(ge))
144            }
145            "Name" => {
146                let name = ob.extract().map_err(|e| extraction_failure("parsing Name expression", &ob, e))?;
147                Ok(Self::Name(name))
148            }
149            "UnaryOp" => {
150                let c = ob.extract().map_err(|e| extraction_failure("extracting UnaryOp in expression", &ob, e))?;
151                Ok(Self::UnaryOp(c))
152            }
153            "BinOp" => {
154                let c = ob.extract().map_err(|e| extraction_failure("extracting BinOp in expression", &ob, e))?;
155                Ok(Self::BinOp(c))
156            }
157            "Lambda" => {
158                let l = ob.extract().map_err(|e| extraction_failure("extracting Lambda in expression", &ob, e))?;
159                Ok(Self::Lambda(l))
160            }
161            "IfExp" => {
162                let i = ob.extract().map_err(|e| extraction_failure("extracting IfExp in expression", &ob, e))?;
163                Ok(Self::IfExp(i))
164            }
165            "Dict" => {
166                let d = ob.extract().map_err(|e| extraction_failure("extracting Dict in expression", &ob, e))?;
167                Ok(Self::Dict(d))
168            }
169            "Set" => {
170                let s = ob.extract().map_err(|e| extraction_failure("extracting Set in expression", &ob, e))?;
171                Ok(Self::Set(s))
172            }
173            "Tuple" => {
174                let t = ob.extract().map_err(|e| extraction_failure("extracting Tuple in expression", &ob, e))?;
175                Ok(Self::Tuple(t))
176            }
177            "Subscript" => {
178                let s = ob.extract().map_err(|e| extraction_failure("extracting Subscript in expression", &ob, e))?;
179                Ok(Self::Subscript(s))
180            }
181            "Starred" => {
182                let s = ob.extract().map_err(|e| extraction_failure("extracting Starred in expression", &ob, e))?;
183                Ok(Self::Starred(s))
184            }
185            "Yield" => {
186                let y = ob.extract().map_err(|e| extraction_failure("extracting Yield in expression", &ob, e))?;
187                Ok(Self::Yield(y))
188            }
189            "YieldFrom" => {
190                let yf = ob.extract().map_err(|e| extraction_failure("extracting YieldFrom in expression", &ob, e))?;
191                Ok(Self::YieldFrom(yf))
192            }
193            "JoinedStr" => {
194                let js = ob.extract().map_err(|e| extraction_failure("extracting JoinedStr in expression", &ob, e))?;
195                Ok(Self::JoinedStr(js))
196            }
197            "FormattedValue" => {
198                let fv = ob.extract().map_err(|e| extraction_failure("extracting FormattedValue in expression", &ob, e))?;
199                Ok(Self::FormattedValue(fv))
200            }
201            _ => {
202                let err_msg = format!(
203                    "Unimplemented expression type {}, {}",
204                    expr_type,
205                    dump(&ob, None)?
206                );
207                Err(pyo3::exceptions::PyValueError::new_err(
208                    ob.error_message("<unknown>", err_msg.as_str()),
209                ))
210            }
211        };
212        r
213    }
214}
215
216impl<'a> CodeGen for ExprType {
217    type Context = CodeGenContext;
218    type Options = PythonOptions;
219    type SymbolTable = SymbolTableScopes;
220
221    fn to_rust(
222        self,
223        ctx: Self::Context,
224        options: Self::Options,
225        symbols: Self::SymbolTable,
226    ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
227        match self {
228            ExprType::Attribute(attribute) => attribute.to_rust(ctx, options, symbols),
229            ExprType::Await(func) => func.to_rust(ctx, options, symbols),
230            ExprType::BinOp(binop) => binop.to_rust(ctx, options, symbols),
231            ExprType::BoolOp(boolop) => boolop.to_rust(ctx, options, symbols),
232            ExprType::Call(call) => call.to_rust(ctx, options, symbols),
233            ExprType::Compare(c) => c.to_rust(ctx, options, symbols),
234            ExprType::Constant(c) => c.to_rust(ctx, options, symbols),
235            ExprType::Lambda(l) => l.to_rust(ctx, options, symbols),
236            ExprType::IfExp(i) => i.to_rust(ctx, options, symbols),
237            ExprType::Dict(d) => d.to_rust(ctx, options, symbols),
238            ExprType::Set(s) => s.to_rust(ctx, options, symbols),
239            ExprType::ListComp(lc) => lc.to_rust(ctx, options, symbols),
240            ExprType::DictComp(dc) => dc.to_rust(ctx, options, symbols),
241            ExprType::SetComp(sc) => sc.to_rust(ctx, options, symbols),
242            ExprType::GeneratorExp(ge) => ge.to_rust(ctx, options, symbols),
243            ExprType::Tuple(t) => t.to_rust(ctx, options, symbols),
244            ExprType::Subscript(s) => s.to_rust(ctx, options, symbols),
245            ExprType::Starred(s) => s.to_rust(ctx, options, symbols),
246            ExprType::Yield(y) => y.to_rust(ctx, options, symbols),
247            ExprType::YieldFrom(yf) => yf.to_rust(ctx, options, symbols),
248            ExprType::JoinedStr(js) => js.to_rust(ctx, options, symbols),
249            ExprType::FormattedValue(fv) => fv.to_rust(ctx, options, symbols),
250            ExprType::List(l) => {
251                let mut elements = Vec::new();
252                let mut has_starred = false;
253                
254                for li in l {
255                    let code = li
256                        .clone()
257                        .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
258                    
259                    // Check if this is a starred expression
260                    if matches!(li, ExprType::Starred(_)) {
261                        has_starred = true;
262                        let code_str = code.to_string();
263                        // Special handling for sys::argv unpacking
264                        if code_str.contains("sys :: argv") {
265                            // Mark that we need special sys::argv handling with a unique marker
266                            elements.push(quote! { __STARRED_ARGV_MARKER__ });
267                        } else {
268                            elements.push(code);
269                        }
270                    } else {
271                        elements.push(code);
272                    }
273                }
274                
275                // If we have starred expressions, handle them specially
276                if has_starred {
277                    let mut final_elements = Vec::new();
278                    let mut has_argv_starred = false;
279                    
280                    for element in elements {
281                        let elem_str = element.to_string();
282                        if elem_str.contains("__STARRED_ARGV_MARKER__") {
283                            has_argv_starred = true;
284                            continue; // Skip the placeholder
285                        } else {
286                            final_elements.push(element);
287                        }
288                    }
289                    
290                    // Build the vector with proper unpacking
291                    if has_argv_starred {
292                        if final_elements.is_empty() {
293                            // Only sys::argv unpacking
294                            Ok(quote! {
295                                (*sys::argv).clone()
296                            })
297                        } else {
298                            // Mix of regular elements and sys::argv unpacking
299                            // Clone each element to avoid ownership issues
300                            Ok(quote! {
301                                {
302                                    let mut vec = Vec::new();
303                                    #(vec.push((#final_elements).clone().to_string());)*
304                                    vec.extend((*sys::argv).iter().cloned());
305                                    vec
306                                }
307                            })
308                        }
309                    } else {
310                        // Other starred expressions (not sys::argv)
311                        Ok(quote! {
312                            vec![#(#final_elements),*]
313                        })
314                    }
315                } else {
316                    // Elements keep their own types: [1, 2, 3] must become a
317                    // Vec<i64>, not a Vec<String>.
318                    Ok(quote! {
319                        vec![#(#elements),*]
320                    })
321                }
322            }
323            ExprType::Name(name) => name.to_rust(ctx, options, symbols),
324            // Python's None is Rust's Option::None: `x = None` initializes
325            // an Option, `f(None)` passes one, `d.get(k)` results compare
326            // against it.
327            ExprType::NoneType(_) => Ok(quote!(None)),
328            ExprType::UnaryOp(operand) => operand.to_rust(ctx, options, symbols),
329
330            _ => {
331                let error = err_from(ExprTypeNotYetImplemented(self));
332                Err(error.into())
333            }
334        }
335    }
336}
337
338/// An Expr only contains a single value key, which leads to the actual expression,
339/// which is one of several types.
340#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
341pub struct Expr {
342    pub value: ExprType,
343    pub ctx: Option<String>,
344    pub lineno: Option<usize>,
345    pub col_offset: Option<usize>,
346    pub end_lineno: Option<usize>,
347    pub end_col_offset: Option<usize>,
348}
349
350impl<'a, 'py> FromPyObject<'a, 'py> for Expr {
351    type Error = pyo3::PyErr;
352    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
353        let err_msg = format!("extracting object value {} in expression", dump(&ob, None)?);
354
355        let ob_value = ob
356            .getattr("value")
357            .map_err(|e| extraction_failure("expression value", &ob, format!("{}: {}", err_msg, e)))?;
358        tracing::debug!("ob_value: {}", dump(&ob_value, None)?);
359
360        // The context is Load, Store, etc. For some types of expressions such as Constants, it does not exist.
361        let ctx: Option<String> = if let Ok(pyany) = ob_value.getattr("ctx") {
362            pyany.get_type().extract().unwrap_or_default()
363        } else {
364            None
365        };
366
367        let mut r = Self {
368            value: ExprType::Unknown,
369            ctx: ctx,
370            lineno: ob.lineno(),
371            col_offset: ob.col_offset(),
372            end_lineno: ob.end_lineno(),
373            end_col_offset: ob.end_col_offset(),
374        };
375
376        let expr_type = ob_value
377            .get_type()
378            .name()
379            .map_err(|e| extraction_failure("expression type name", &ob, e))?;
380        tracing::debug!(
381            "expression type: {}, value: {}",
382            expr_type,
383            dump(&ob_value, None)?
384        );
385        match expr_type.extract::<String>()?.as_str() {
386            "Attribute" => {
387                let a = ob_value
388                    .extract()
389                    .map_err(|e| extraction_failure("Attribute expression", &ob_value, e))?;
390                r.value = ExprType::Attribute(a);
391                Ok(r)
392            }
393            "Await" => {
394                let a = ob_value
395                    .extract()
396                    .map_err(|e| extraction_failure("Await expression", &ob_value, e))?;
397                r.value = ExprType::Await(a);
398                Ok(r)
399            }
400            "BinOp" => {
401                let c = ob_value
402                    .extract()
403                    .map_err(|e| extraction_failure("BinOp expression", &ob_value, e))?;
404                r.value = ExprType::BinOp(c);
405                Ok(r)
406            }
407            "BoolOp" => {
408                let c = ob_value
409                    .extract()
410                    .map_err(|e| extraction_failure("BoolOp expression", &ob_value, e))?;
411                r.value = ExprType::BoolOp(c);
412                Ok(r)
413            }
414            "Call" => {
415                let et = ob_value
416                    .extract()
417                    .map_err(|e| extraction_failure("Call expression", &ob_value, e))?;
418                r.value = ExprType::Call(et);
419                Ok(r)
420            }
421            "Constant" => {
422                let c = ob_value
423                    .extract()
424                    .map_err(|e| extraction_failure("Constant expression", &ob_value, e))?;
425                r.value = ExprType::Constant(c);
426                Ok(r)
427            }
428            "Compare" => {
429                let c = ob_value
430                    .extract()
431                    .map_err(|e| extraction_failure("Compare expression", &ob_value, e))?;
432                r.value = ExprType::Compare(c);
433                Ok(r)
434            }
435            "List" => {
436                // Extract the list elements using the 'elts' attribute
437                let elts_attr = ob_value
438                    .getattr("elts")
439                    .map_err(|e| extraction_failure("list elements", &ob_value, e))?;
440                let elts_vec: Vec<Bound<PyAny>> = elts_attr
441                    .extract()
442                    .map_err(|e| extraction_failure("list elements", &ob_value, e))?;
443
444                // Convert each element to ExprType
445                let mut expr_list = Vec::new();
446                for elt in elts_vec {
447                    let expr: ExprType = elt
448                        .extract()
449                        .map_err(|e| extraction_failure("list element", &elt, e))?;
450                    expr_list.push(expr);
451                }
452                
453                r.value = ExprType::List(expr_list);
454                Ok(r)
455            }
456            "Name" => {
457                let name = ob_value
458                    .extract()
459                    .map_err(|e| extraction_failure("Name expression", &ob_value, e))?;
460                r.value = ExprType::Name(name);
461                Ok(r)
462            }
463            "UnaryOp" => {
464                let c = ob_value
465                    .extract()
466                    .map_err(|e| extraction_failure("UnaryOp expression", &ob_value, e))?;
467                r.value = ExprType::UnaryOp(c);
468                Ok(r)
469            }
470            "Lambda" => {
471                let l = ob_value
472                    .extract()
473                    .map_err(|e| extraction_failure("Lambda expression", &ob_value, e))?;
474                r.value = ExprType::Lambda(l);
475                Ok(r)
476            }
477            "IfExp" => {
478                let i = ob_value
479                    .extract()
480                    .map_err(|e| extraction_failure("IfExp expression", &ob_value, e))?;
481                r.value = ExprType::IfExp(i);
482                Ok(r)
483            }
484            "Dict" => {
485                let d = ob_value
486                    .extract()
487                    .map_err(|e| extraction_failure("Dict expression", &ob_value, e))?;
488                r.value = ExprType::Dict(d);
489                Ok(r)
490            }
491            "Set" => {
492                let s = ob_value
493                    .extract()
494                    .map_err(|e| extraction_failure("Set expression", &ob_value, e))?;
495                r.value = ExprType::Set(s);
496                Ok(r)
497            }
498            "Tuple" => {
499                let t = ob_value
500                    .extract()
501                    .map_err(|e| extraction_failure("Tuple expression", &ob_value, e))?;
502                r.value = ExprType::Tuple(t);
503                Ok(r)
504            }
505            "Subscript" => {
506                let s = ob_value
507                    .extract()
508                    .map_err(|e| extraction_failure("Subscript expression", &ob_value, e))?;
509                r.value = ExprType::Subscript(s);
510                Ok(r)
511            }
512            "Yield" => {
513                let y = ob_value
514                    .extract()
515                    .map_err(|e| extraction_failure("Yield expression", &ob_value, e))?;
516                r.value = ExprType::Yield(y);
517                Ok(r)
518            }
519            "YieldFrom" => {
520                let yf = ob_value
521                    .extract()
522                    .map_err(|e| extraction_failure("YieldFrom expression", &ob_value, e))?;
523                r.value = ExprType::YieldFrom(yf);
524                Ok(r)
525            }
526            "JoinedStr" => {
527                let js = ob_value
528                    .extract()
529                    .map_err(|e| extraction_failure("JoinedStr expression", &ob_value, e))?;
530                r.value = ExprType::JoinedStr(js);
531                Ok(r)
532            }
533            "FormattedValue" => {
534                let fv = ob_value
535                    .extract()
536                    .map_err(|e| extraction_failure("FormattedValue expression", &ob_value, e))?;
537                r.value = ExprType::FormattedValue(fv);
538                Ok(r)
539            }
540            "GeneratorExp" => {
541                let ge = ob_value
542                    .extract()
543                    .map_err(|e| extraction_failure("GeneratorExp expression", &ob_value, e))?;
544                r.value = ExprType::GeneratorExp(ge);
545                Ok(r)
546            }
547            // In sitations where an expression is optional, we may see a NoneType expressions.
548            "NoneType" => {
549                r.value = ExprType::NoneType(Constant(None));
550                Ok(r)
551            }
552            _ => {
553                let err_msg = format!(
554                    "Unimplemented expression type {}, {}",
555                    expr_type,
556                    dump(&ob, None)?
557                );
558                Err(pyo3::exceptions::PyValueError::new_err(
559                    ob.error_message("<unknown>", err_msg.as_str()),
560                ))
561            }
562        }
563    }
564}
565
566impl CodeGen for Expr {
567    type Context = CodeGenContext;
568    type Options = PythonOptions;
569    type SymbolTable = SymbolTableScopes;
570
571    fn to_rust(
572        self,
573        ctx: Self::Context,
574        options: Self::Options,
575        symbols: Self::SymbolTable,
576    ) -> std::result::Result<TokenStream, Box<dyn std::error::Error>> {
577        // Delegate to the (complete) ExprType dispatch rather than keeping a
578        // second, drifting copy of the match here. NoneType statements
579        // generate no code.
580        if matches!(self.value, ExprType::NoneType(_)) {
581            return Ok(quote!());
582        }
583        self.value.to_rust(ctx, options, symbols)
584    }
585}
586
587impl Node for Expr {
588    fn lineno(&self) -> Option<usize> {
589        self.lineno
590    }
591
592    fn col_offset(&self) -> Option<usize> {
593        self.col_offset
594    }
595
596    fn end_lineno(&self) -> Option<usize> {
597        self.end_lineno
598    }
599
600    fn end_col_offset(&self) -> Option<usize> {
601        self.end_col_offset
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use super::*;
608
609    #[test]
610    fn check_call_expression() {
611        let expression = crate::parse("test()", "test.py").unwrap();
612        let mut options = PythonOptions::default();
613        options.with_std_python = false;
614        let symbols = SymbolTableScopes::new();
615        let tokens = expression
616            .clone()
617            .to_rust(CodeGenContext::Module("test".to_string()), options, symbols)
618            .unwrap();
619        assert_eq!(
620            tokens.to_string(),
621            "fn __module_init__ () -> Result < () , PyException > { test () ; Ok (()) } \
622             fn main () { if let Err (e) = __module_init__ () { eprintln ! (\"{}\" , e) ; \
623             std :: process :: exit (1) ; } }"
624        );
625    }
626}
627
628/// Lower an expression in condition position (if/while/ternary/assert
629/// tests): Python implicitly calls bool() on it. Boolean operators recurse
630/// into their operands and `not` negates a condition; comparisons already
631/// yield bool; anything else is wrapped in stdpython's Truthy::is_truthy,
632/// giving Python's truth table (empty string/collection and zero are
633/// false).
634pub fn condition_to_rust(
635    expr: &ExprType,
636    ctx: CodeGenContext,
637    options: PythonOptions,
638    symbols: SymbolTableScopes,
639) -> Result<TokenStream, Box<dyn std::error::Error>> {
640    match expr {
641        ExprType::BoolOp(op)
642            if matches!(op.op, crate::BoolOps::And | crate::BoolOps::Or) =>
643        {
644            let mut parts = Vec::new();
645            for value in &op.values {
646                parts.push(condition_to_rust(
647                    value,
648                    ctx.clone(),
649                    options.clone(),
650                    symbols.clone(),
651                )?);
652            }
653            Ok(match op.op {
654                crate::BoolOps::And => quote!(#((#parts))&&*),
655                _ => quote!(#((#parts))||*),
656            })
657        }
658        ExprType::UnaryOp(u) if matches!(u.op, crate::Ops::Not) => {
659            let inner = condition_to_rust(&u.operand, ctx, options, symbols)?;
660            Ok(quote!(!(#inner)))
661        }
662        // Comparisons (including `in` and `is None`) already produce bool.
663        ExprType::Compare(_) => expr.clone().to_rust(ctx, options, symbols),
664        // Bool literals are already bool.
665        ExprType::Constant(c) if matches!(&c.0, Some(litrs::Literal::Bool(_))) => {
666            expr.clone().to_rust(ctx, options, symbols)
667        }
668        other => {
669            let tokens = other.clone().to_rust(ctx, options, symbols)?;
670            Ok(quote!((#tokens).is_truthy()))
671        }
672    }
673}