python_ast/ast/tree/
bool_ops.rs

1use proc_macro2::TokenStream;
2use pyo3::{Bound, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods, types::PyTypeMethods};
3use quote::quote;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    dump, CodeGen, CodeGenContext, Error, ExprType, Node, PythonOptions, SymbolTableScopes,
8};
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
11pub enum BoolOps {
12    And,
13    Or,
14    Unknown,
15}
16
17impl<'a> FromPyObject<'a> for BoolOps {
18    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
19        let op_type = ob.get_type().name().expect(
20            ob.error_message(
21                "<unknown>",
22                format!("extracting type name {:?} for boolean operator", ob),
23            )
24            .as_str(),
25        );
26
27        let op_type_str: String = op_type.extract()?;
28        let op = match op_type_str.as_str() {
29            "And" => BoolOps::And,
30            "Or" => BoolOps::Or,
31            _ => {
32                log::debug!("Found unknown BoolOp {:?}", op_type_str);
33                BoolOps::Unknown
34            }
35        };
36
37        Ok(op)
38    }
39}
40
41#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
42pub struct BoolOp {
43    op: BoolOps,
44    left: Box<ExprType>,
45    right: Box<ExprType>,
46}
47
48impl<'a> FromPyObject<'a> for BoolOp {
49    fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult<Self> {
50        log::debug!("ob: {}", dump(ob, None)?);
51        let op = ob.getattr("op").expect(
52            ob.error_message("<unknown>", "error getting unary operator")
53                .as_str(),
54        );
55
56        let op_type = op.get_type().name().expect(
57            ob.error_message(
58                "<unknown>",
59                format!("extracting type name {:?} for binary operator", op),
60            )
61            .as_str(),
62        );
63
64        let values = ob.getattr("values").expect(
65            ob.error_message("<unknown>", "error getting binary operand")
66                .as_str(),
67        );
68
69        log::debug!("BoolOps values: {}", dump(&values, None)?);
70
71        let value: Vec<ExprType> = values.extract().expect("getting values from BoolOp");
72        let left = value[0].clone();
73        let right = value[1].clone();
74
75        let op_type_str: String = op_type.extract()?;
76        let op = match op_type_str.as_str() {
77            "And" => BoolOps::And,
78            "Or" => BoolOps::Or,
79
80            _ => {
81                log::debug!("Found unknown BoolOp {:?}", op);
82                BoolOps::Unknown
83            }
84        };
85
86        log::debug!(
87            "left: {:?}, right: {:?}, op: {:?}/{:?}",
88            left,
89            right,
90            op_type,
91            op
92        );
93
94        return Ok(BoolOp {
95            op: op,
96            left: Box::new(left),
97            right: Box::new(right),
98        });
99    }
100}
101
102impl<'a> CodeGen for BoolOp {
103    type Context = CodeGenContext;
104    type Options = PythonOptions;
105    type SymbolTable = SymbolTableScopes;
106
107    fn to_rust(
108        self,
109        ctx: Self::Context,
110        options: Self::Options,
111        symbols: Self::SymbolTable,
112    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
113        let left = self
114            .left
115            .clone()
116            .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
117        let right = self
118            .right
119            .clone()
120            .to_rust(ctx.clone(), options.clone(), symbols.clone())?;
121            
122        // Python's boolean operators are different from Rust's - they return operands, not booleans
123        // For now, we'll use a simplified approach that works for common cases
124        let right_str = right.to_string();
125        
126        match self.op {
127            BoolOps::Or => {
128                if right_str.trim() == "None" {
129                    // Special case for `x or None` - just return the left operand
130                    // This avoids the type mismatch error with || None
131                    Ok(quote!(#left))
132                } else {
133                    // Use simple boolean OR for other cases
134                    // TODO: Implement proper Python `or` semantics
135                    Ok(quote!((#left) || (#right)))
136                }
137            },
138            BoolOps::And => {
139                // Use simple boolean AND
140                // TODO: Implement proper Python `and` semantics
141                Ok(quote!((#left) && (#right)))
142            },
143
144            _ => Err(Error::BoolOpNotYetImplemented(self).into()),
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_and() {
155        let options = PythonOptions::default();
156        let result = crate::parse("1 and 2", "test_case.py").unwrap();
157        log::info!("Python tree: {:?}", result);
158        //log::info!("{}", result.to_rust().unwrap());
159
160        let code = result
161            .to_rust(
162                CodeGenContext::Module("test_case".to_string()),
163                options,
164                SymbolTableScopes::new(),
165            )
166            .unwrap();
167        log::info!("module: {:?}", code);
168    }
169
170    #[test]
171    fn test_or() {
172        let options = PythonOptions::default();
173        let result = crate::parse("1 or 2", "test_case.py").unwrap();
174        log::info!("Python tree: {:?}", result);
175        //log::info!("{}", result);
176
177        let code = result
178            .to_rust(
179                CodeGenContext::Module("test_case".to_string()),
180                options,
181                SymbolTableScopes::new(),
182            )
183            .unwrap();
184        log::info!("module: {:?}", code);
185    }
186}