Skip to main content

python_ast/ast/tree/
module.rs

1use std::{collections::HashMap, default::Default};
2
3use tracing::info;
4use proc_macro2::TokenStream;
5use pyo3::{Borrowed, FromPyObject, PyAny, PyResult, prelude::PyAnyMethods};
6use quote::{format_ident, quote};
7use serde::{Deserialize, Serialize};
8
9use crate::{CodeGen, CodeGenContext, Name, Object, PythonOptions, Statement, StatementType, ExprType, SymbolTableScopes};
10
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub enum Type {
14    Unimplemented,
15}
16
17impl<'a, 'py> FromPyObject<'a, 'py> for Type {
18    type Error = pyo3::PyErr;
19    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
20        info!("Type: {:?}", ob);
21        Ok(Type::Unimplemented)
22    }
23}
24
25/// Represents a module as imported from an ast. See the Module struct for the processed module.
26#[derive(Clone, Debug, Default, Serialize, Deserialize)]
27pub struct RawModule {
28    pub body: Vec<Statement>,
29    pub type_ignores: Vec<Type>,
30}
31
32// Extracted manually (not via derive) so a failing statement's precise error
33// — which names the construct and its line — propagates instead of being
34// replaced by a generic "failed to extract field RawModule.body" message.
35impl<'a, 'py> FromPyObject<'a, 'py> for RawModule {
36    type Error = pyo3::PyErr;
37    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
38        let body_attr = ob
39            .getattr("body")
40            .map_err(|e| crate::extraction_failure("module body", &ob, e))?;
41        let body_list: Vec<pyo3::Bound<PyAny>> = body_attr
42            .extract()
43            .map_err(|e| crate::extraction_failure("module body", &ob, e))?;
44
45        let mut body = Vec::with_capacity(body_list.len());
46        for stmt in &body_list {
47            body.push(Statement::extract(stmt.as_borrowed())?);
48        }
49
50        let type_ignores = ob
51            .getattr("type_ignores")
52            .and_then(|t| t.extract())
53            .unwrap_or_default();
54
55        Ok(Self { body, type_ignores })
56    }
57}
58
59/// Represents a module as imported from an ast.
60#[derive(Clone, Debug, Default, Serialize, Deserialize)]
61pub struct Module {
62    pub raw: RawModule,
63    pub name: Option<Name>,
64    pub doc: Option<String>,
65    pub filename: Option<String>,
66    pub attributes: HashMap<Name, String>,
67}
68
69impl<'a, 'py> FromPyObject<'a, 'py> for Module {
70    type Error = pyo3::PyErr;
71    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
72        // RawModule's extraction already produces precise per-statement
73        // errors; don't re-wrap them here.
74        let raw_module = ob.extract()?;
75
76        Ok(Self {
77            raw: raw_module,
78            ..Default::default()
79        })
80    }
81}
82
83impl CodeGen for Module {
84    type Context = CodeGenContext;
85    type Options = PythonOptions;
86    type SymbolTable = SymbolTableScopes;
87
88    fn find_symbols(self, symbols: Self::SymbolTable) -> Self::SymbolTable {
89        let mut symbols = symbols;
90        symbols.new_scope();
91        for s in self.raw.body {
92            symbols = s.clone().find_symbols(symbols);
93        }
94        symbols
95    }
96
97    fn to_rust(
98        self,
99        ctx: Self::Context,
100        options: Self::Options,
101        symbols: Self::SymbolTable,
102    ) -> Result<TokenStream, Box<dyn std::error::Error>> {
103        let mut stream = TokenStream::new();
104
105        // Capture the module's source filename before fields of `self` are
106        // moved, so statement errors can point at the user's Python file.
107        let module_filename = self
108            .filename
109            .clone()
110            .or_else(|| self.name.as_ref().map(|n| format!("{}.py", n.id)))
111            .unwrap_or_else(|| "<module>".to_string());
112
113        // Add module-level documentation if available and not just an expression
114        if let Some(docstring) = self.get_module_docstring() {
115            // Only add module docs if there are multiple statements or if this seems to be a real module docstring
116            if self.raw.body.len() > 1 || self.looks_like_module_docstring() {
117                let doc_lines: Vec<_> = docstring
118                    .lines()
119                    .map(|line| {
120                        if line.trim().is_empty() {
121                            quote! { #![doc = ""] }
122                        } else {
123                            let doc_line = format!("{}", line);
124                            quote! { #![doc = #doc_line] }
125                        }
126                    })
127                    .collect();
128                stream.extend(quote! { #(#doc_lines)* });
129                
130                // Add generated by comment only when we have actual module docs
131                let generated_comment =
132                    format!("Generated from Python file: {}", module_filename);
133                stream.extend(quote! { #![doc = #generated_comment] });
134            }
135        }
136        
137        if options.with_std_python {
138            // For imports, always use "stdpython" since that's the actual crate name
139            // The runtime specification is just for dependency management
140            stream.extend(quote!(use stdpython::*;));
141        }
142
143        // Under no_std the prelude has no String/Vec/format!: bring the
144        // alloc surface generated code leans on into scope. Emitted per
145        // module — each module file in the generated crate lowers through
146        // here — while `extern crate alloc` itself binds locally too, so
147        // the imports resolve regardless of what the crate root declares.
148        // allow(unused_imports): this is harness plumbing, and the lint
149        // posture should keep surfacing only source-Python weaknesses.
150        if options.no_std {
151            stream.extend(quote! {
152                extern crate alloc;
153                #[allow(unused_imports)]
154                use alloc::{
155                    format, vec, borrow::ToOwned, string::String, string::ToString,
156                    vec::Vec,
157                };
158            });
159        }
160        
161        // Add async runtime dependency if async functions are detected
162        // We'll check this early so we can add the import at the top
163        let needs_async_runtime = self.raw.body.iter().any(|s| {
164            matches!(&s.statement, crate::StatementType::AsyncFunctionDef(_))
165        });
166        
167        if needs_async_runtime {
168            let runtime_import = format_ident!("{}", options.async_runtime.import());
169            stream.extend(quote!(use #runtime_import;));
170        }
171        
172        let mut main_body_stmts = Vec::new();
173        let mut has_main_code = false;
174        let mut has_async_functions = false;
175        let mut module_init_stmts = Vec::new();
176        let mut has_module_init_code = false;
177        let mut is_simple_main_call_pattern = false;
178        // The raw statements behind main_body_stmts/module_init_stmts, kept
179        // so assigned names can be hoisted to declarations (assignments
180        // lower to plain stores; see collect_assigned_names).
181        let mut main_body_raw: Vec<crate::Statement> = Vec::new();
182        let mut module_init_raw: Vec<crate::Statement> = Vec::new();
183
184        // Module-level names assigned exactly once from a literal, with no
185        // other store ANYWHERE in module scope, are CONSTANTS: they lower
186        // to static items so functions in the module can see them (Python
187        // module globals are visible everywhere; a store hidden inside
188        // __module_init__ is not). The tally recurses through module-level
189        // control flow — `DEBUG = False` conditionally overwritten inside
190        // an `if` is NOT a constant, and treating it as one would silently
191        // freeze the original value. (Function bodies need no scan: without
192        // `global` — unsupported — their assignments create locals.)
193        let mut module_assign_counts: std::collections::HashMap<String, usize> =
194            std::collections::HashMap::new();
195        count_module_stores(&self.raw.body, &mut module_assign_counts);
196
197        // A user `main` that returns a value cannot serve as the Rust entry
198        // point directly (Result<i64, _> does not implement Termination);
199        // route it through the renaming wrapper, which discards the value —
200        // exactly what Python's `if __name__: main()` does.
201        let user_main_returns_value = self.raw.body.iter().any(|s| {
202            matches!(
203                &s.statement,
204                crate::StatementType::FunctionDef(f)
205                    if f.name == "main" && f.resolved_return_type().is_some()
206            )
207        });
208
209        for s in self.raw.body {
210            // Check if this statement is an async function
211            if let crate::StatementType::AsyncFunctionDef(_) = &s.statement {
212                has_async_functions = true;
213            }
214
215            // Check for if __name__ == "__main__" blocks at the AST level before generating code
216            if let crate::StatementType::If(if_stmt) = &s.statement {
217                let test_str = format!("{:?}", if_stmt.test);
218                if test_str.contains("__name__") && test_str.contains("__main__") {
219                    // Check if this is a simple main() call pattern
220                    let is_simple_main_call = Self::is_simple_main_call_block(&if_stmt.body)
221                        && !user_main_returns_value;
222                    
223                    if is_simple_main_call {
224                        // For simple main() calls, we'll use the user's main function directly
225                        // Set a flag to indicate we should not rename the main function
226                        has_main_code = true;
227                        is_simple_main_call_pattern = true;
228                        // Don't collect the main body statements - we'll use user's main directly
229                    } else {
230                        // This is a complex __name__ == "__main__" block - collect its body for main function
231                        for body_stmt in &if_stmt.body {
232                            let stmt_token = body_stmt
233                                .clone()
234                                .to_rust(ctx.clone(), options.clone(), symbols.clone())
235                                .map_err(|e| wrap_module_error(&module_filename, e))?;
236                            if !stmt_token.to_string().trim().is_empty() {
237                                main_body_stmts.push(stmt_token);
238                                main_body_raw.push(body_stmt.clone());
239                                has_main_code = true;
240                            }
241                        }
242                    }
243                    // Skip generating this if statement - we've processed its contents
244                    continue;
245                }
246            }
247            
248            // Module-level constants become static items visible to every
249            // function in the module.
250            if let crate::StatementType::Assign(a) = &s.statement {
251                if let [crate::ExprType::Name(n)] = a.targets.as_slice() {
252                    if module_assign_counts.get(&n.id) == Some(&1) {
253                        if let Some(ty) = const_static_type(&a.value) {
254                            let ident = crate::safe_ident(&n.id);
255                            let value = a.value.clone().to_rust(
256                                ctx.clone(),
257                                options.clone(),
258                                symbols.clone(),
259                            )?;
260                            stream.extend(quote!(pub static #ident: #ty = #value;));
261                            continue;
262                        }
263                    }
264                }
265            }
266
267            // Categorize statements into declarations vs executable code
268            let is_declaration = Self::is_declaration_statement(&s.statement);
269
270            let statement = s
271                .clone()
272                .to_rust(ctx.clone(), options.clone(), symbols.clone())
273                .map_err(|e| wrap_module_error(&module_filename, e))?;
274            
275            if statement.to_string() != "" {
276                if is_declaration {
277                    // Declarations go at module level (functions, classes, imports)
278                    stream.extend(statement);
279                } else {
280                    // Executable statements go in module initialization function
281                    module_init_stmts.push(statement);
282                    module_init_raw.push(s.clone());
283                    has_module_init_code = true;
284                }
285            }
286        }
287
288        // Hoist assigned names to declarations at the top of each generated
289        // scope (assignments themselves lower to plain stores).
290        let init_decls = hoisted_declarations(&module_init_raw, &ctx, &symbols);
291        if !init_decls.is_empty() {
292            module_init_stmts.insert(0, init_decls);
293        }
294        let main_decls = hoisted_declarations(&main_body_raw, &ctx, &symbols);
295        if !main_decls.is_empty() {
296            main_body_stmts.insert(0, main_decls);
297        }
298
299        // Generate module initialization function if needed. Like all
300        // generated functions it returns Result so module-level raises and
301        // calls propagate.
302        if has_module_init_code {
303            stream.extend(quote! {
304                fn __module_init__() -> Result<(), PyException> {
305                    #(#module_init_stmts;)*
306                    Ok(())
307                }
308            });
309        }
310        
311        // A `__main__` block wants a process entry point, and a no_std
312        // target has no OS to enter from: refuse loudly instead of emitting
313        // a fn main() that cannot link.
314        if has_main_code && options.no_std {
315            return Err(
316                "`if __name__ == \"__main__\":` needs a process entry point, which \
317                 the no_std profile does not provide; remove the block (convert the \
318                 module as a library) or convert without the no_std profile"
319                    .to_string()
320                    .into(),
321            );
322        }
323
324        // If we collected any main code, generate a single consolidated main function
325        if has_main_code {
326            if is_simple_main_call_pattern {
327                // Simple main() call pattern - use user's main function directly as Rust entry point
328                // Don't rename the user's main function, just add module init call if needed
329                let stream_str = stream.to_string();
330                
331                // Check if the user's main function is async
332                let user_main_is_async = stream_str.contains("pub async fn main (");
333                
334                if user_main_is_async {
335                    // User's async main becomes the Rust entry point
336                    let runtime_attr = options.async_runtime.main_attribute();
337                    let attr_tokens: proc_macro2::TokenStream = runtime_attr.parse()
338                        .unwrap_or_else(|_| quote!(tokio::main)); // fallback to tokio::main
339                    
340                    // Replace the user's function signature and add attributes
341                    let new_stream_str = stream_str
342                        .replace("pub async fn main (", &format!("#[{}] async fn main(", runtime_attr));
343                    stream = new_stream_str.parse::<proc_macro2::TokenStream>()
344                        .unwrap_or_else(|_| stream);
345                        
346                    // If we have module init code, we need to modify the user's main to call it first
347                    if has_module_init_code {
348                        // This is more complex - we'd need to modify the user's main function body
349                        // For now, let's fall back to the rename approach for async functions with module init
350                        let renamed_stream_str = Self::rename_main_function_and_references(&stream_str);
351                        stream = renamed_stream_str.parse::<proc_macro2::TokenStream>()
352                            .unwrap_or_else(|_| stream);
353
354                        stream.extend(quote! {
355                            #[#attr_tokens]
356                            async fn main() {
357                                let __rython_result: Result<(), PyException> = async {
358                                    __module_init__()?;
359                                    python_main().await?;
360                                    Ok(())
361                                }.await;
362                                if let Err(e) = __rython_result {
363                                    eprintln!("{}", e);
364                                    std::process::exit(1);
365                                }
366                            }
367                        });
368                    }
369                } else {
370                    // User's sync main becomes the Rust entry point
371                    // Need to modify the function to match Rust main signature requirements
372                    let new_stream_str = Self::convert_python_main_to_rust_entry_point(&stream_str);
373                    stream = new_stream_str.parse::<proc_macro2::TokenStream>()
374                        .unwrap_or_else(|_| stream);
375                    
376                    // If we have module init code, we need to modify the user's main to call it first
377                    if has_module_init_code {
378                        // For simplicity, we'll use the rename approach when module init is needed
379                        let renamed_stream_str = Self::rename_main_function_and_references(&stream_str);
380                        stream = renamed_stream_str.parse::<proc_macro2::TokenStream>()
381                            .unwrap_or_else(|_| stream);
382
383                        stream.extend(quote! {
384                            fn main() {
385                                let __rython_result = (|| -> Result<(), PyException> {
386                                    __module_init__()?;
387                                    python_main()?;
388                                    Ok(())
389                                })();
390                                if let Err(e) = __rython_result {
391                                    eprintln!("{}", e);
392                                    std::process::exit(1);
393                                }
394                            }
395                        });
396                    }
397                }
398            } else {
399                // Complex main block - use existing behavior (rename user's main)
400                let stream_str = stream.to_string();
401                let has_python_main = stream_str.contains("pub fn main (") || stream_str.contains("pub async fn main (");
402                
403                if has_python_main {
404                    // Rename the Python function to avoid conflict with Rust entry point
405                    let new_stream_str = Self::rename_main_function_and_references(&stream_str);
406                    stream = new_stream_str.parse::<proc_macro2::TokenStream>()
407                        .unwrap_or_else(|_| stream);
408                    
409                    // Update main_body_stmts to call python_main instead of main
410                    for stmt in &mut main_body_stmts {
411                        let stmt_str = stmt.to_string();
412                        let updated_stmt_str = Self::update_main_references(&stmt_str);
413                        if updated_stmt_str != stmt_str {
414                            if let Ok(new_stmt) = updated_stmt_str.parse::<proc_macro2::TokenStream>() {
415                                *stmt = new_stmt;
416                            }
417                        }
418                    }
419                }
420                
421                // Generate the Rust entry point as main() - async if needed
422                if needs_async_runtime || has_async_functions {
423                    // Parse the runtime attribute string into tokens
424                    let runtime_attr = options.async_runtime.main_attribute();
425                    let attr_tokens: proc_macro2::TokenStream = runtime_attr.parse()
426                        .unwrap_or_else(|_| quote!(tokio::main)); // fallback to tokio::main
427                    
428                    let init_call = if has_module_init_code {
429                        quote!(__module_init__()?;)
430                    } else {
431                        quote!()
432                    };
433                    stream.extend(quote! {
434                        #[#attr_tokens]
435                        async fn main() {
436                            let __rython_result: Result<(), PyException> = async {
437                                #init_call
438                                #(#main_body_stmts;)*
439                                Ok(())
440                            }.await;
441                            if let Err(e) = __rython_result {
442                                eprintln!("{}", e);
443                                std::process::exit(1);
444                            }
445                        }
446                    });
447                } else {
448                    let init_call = if has_module_init_code {
449                        quote!(__module_init__()?;)
450                    } else {
451                        quote!()
452                    };
453                    stream.extend(quote! {
454                        fn main() {
455                            let __rython_result = (|| -> Result<(), PyException> {
456                                #init_call
457                                #(#main_body_stmts;)*
458                                Ok(())
459                            })();
460                            if let Err(e) = __rython_result {
461                                eprintln!("{}", e);
462                                std::process::exit(1);
463                            }
464                        }
465                    });
466                }
467            }
468        } else if has_module_init_code {
469            // No main block, but we have module initialization code
470            // Generate a main function that just runs module initialization
471            stream.extend(quote! {
472                fn main() {
473                    if let Err(e) = __module_init__() {
474                        eprintln!("{}", e);
475                        std::process::exit(1);
476                    }
477                }
478            });
479        }
480        Ok(stream)
481    }
482}
483
484/// Count stores to each name across MODULE scope: top-level assignments
485/// plus everything nested in module-level control flow — if/while/for
486/// bodies (and the for target itself, which rebinds every iteration),
487/// with bodies and their `as` targets, try bodies and handlers. Nested
488/// stores count double so a name assigned once at top level and again in
489/// a branch never tallies as once-assigned. Function and class bodies are
490/// their own scopes and are not walked.
491fn count_module_stores(
492    body: &[crate::Statement],
493    counts: &mut std::collections::HashMap<String, usize>,
494) {
495    fn bump_target(target: &crate::ExprType, by: usize, counts: &mut std::collections::HashMap<String, usize>) {
496        match target {
497            crate::ExprType::Name(n) => {
498                *counts.entry(n.id.clone()).or_insert(0) += by;
499            }
500            crate::ExprType::Tuple(t) => {
501                for elt in &t.elts {
502                    bump_target(elt, by, counts);
503                }
504            }
505            _ => {}
506        }
507    }
508    for s in body {
509        match &s.statement {
510            crate::StatementType::Assign(a) => {
511                for target in &a.targets {
512                    bump_target(target, 1, counts);
513                }
514            }
515            crate::StatementType::AugAssign(a) => bump_target(&a.target, 2, counts),
516            crate::StatementType::If(i) => {
517                let mut nested = std::collections::HashMap::new();
518                count_module_stores(&i.body, &mut nested);
519                count_module_stores(&i.orelse, &mut nested);
520                for (name, n) in nested {
521                    *counts.entry(name).or_insert(0) += n * 2;
522                }
523            }
524            crate::StatementType::While(w) => {
525                let mut nested = std::collections::HashMap::new();
526                count_module_stores(&w.body, &mut nested);
527                count_module_stores(&w.orelse, &mut nested);
528                for (name, n) in nested {
529                    *counts.entry(name).or_insert(0) += n * 2;
530                }
531            }
532            crate::StatementType::For(f) => {
533                bump_target(&f.target, 2, counts);
534                let mut nested = std::collections::HashMap::new();
535                count_module_stores(&f.body, &mut nested);
536                count_module_stores(&f.orelse, &mut nested);
537                for (name, n) in nested {
538                    *counts.entry(name).or_insert(0) += n * 2;
539                }
540            }
541            crate::StatementType::With(w) => {
542                for item in &w.items {
543                    if let Some(vars) = &item.optional_vars {
544                        bump_target(vars, 2, counts);
545                    }
546                }
547                let mut nested = std::collections::HashMap::new();
548                count_module_stores(&w.body, &mut nested);
549                for (name, n) in nested {
550                    *counts.entry(name).or_insert(0) += n * 2;
551                }
552            }
553            crate::StatementType::Try(t) => {
554                let mut nested = std::collections::HashMap::new();
555                count_module_stores(&t.body, &mut nested);
556                for h in &t.handlers {
557                    count_module_stores(&h.body, &mut nested);
558                }
559                count_module_stores(&t.orelse, &mut nested);
560                count_module_stores(&t.finalbody, &mut nested);
561                for (name, n) in nested {
562                    *counts.entry(name).or_insert(0) += n * 2;
563                }
564            }
565            // Function and class bodies are separate scopes.
566            _ => {}
567        }
568    }
569}
570
571/// The static-item type for a module-level constant, when its value is a
572/// literal a static can hold (numbers, bools, strings — including a
573/// leading unary minus). Non-literal or reassigned module globals keep
574/// the old __module_init__ lowering, where referencing them from a
575/// function is a loud compile error rather than a silent divergence.
576fn const_static_type(value: &crate::ExprType) -> Option<TokenStream> {
577    match value {
578        crate::ExprType::Constant(c) => match &c.0 {
579            Some(litrs::Literal::Integer(_)) => Some(quote!(i64)),
580            Some(litrs::Literal::Float(_)) => Some(quote!(f64)),
581            Some(litrs::Literal::Bool(_)) => Some(quote!(bool)),
582            Some(litrs::Literal::String(_)) => Some(quote!(&'static str)),
583            _ => None,
584        },
585        crate::ExprType::UnaryOp(op) => {
586            if !matches!(op.op, crate::ast::tree::unary_op::Ops::USub) {
587                return None;
588            }
589            match const_static_type(&op.operand) {
590                Some(ty) if ty.to_string() == "i64" || ty.to_string() == "f64" => Some(ty),
591                _ => None,
592            }
593        }
594        _ => None,
595    }
596}
597
598/// Declarations for every name assigned in a statement list, so
599/// nested-block assignments store into scope-level variables instead of
600/// creating shadowing bindings. Scope analysis decides which need `mut`.
601fn hoisted_declarations(
602    body: &[crate::Statement],
603    ctx: &crate::CodeGenContext,
604    symbols: &crate::SymbolTableScopes,
605) -> TokenStream {
606    // Class-aware mutation facts need the block's own assignments in the
607    // symbol table (`c = Counter(...)` then `c.bump()` needs `c` mutable).
608    let mut symbols = symbols.clone();
609    for s in body {
610        symbols = s.clone().find_symbols(symbols);
611    }
612    let scope =
613        crate::analyze_scope_with(body, &[], &crate::class_call_resolver(ctx, &symbols));
614    let mut out = TokenStream::new();
615    for name in &scope.assigned {
616        let ident = crate::safe_ident(name);
617        if scope.needs_mut.contains(name) {
618            out.extend(quote!(let mut #ident;));
619        } else {
620            out.extend(quote!(let #ident;));
621        }
622    }
623    out
624}
625
626/// Rebuild a statement-level codegen error so it points at the module's real
627/// source file. Statement errors carry a `<module>` placeholder filename in
628/// their location; this substitutes the actual filename and preserves the
629/// structured fields (message, help) so downstream consumers — the proc
630/// macro in particular — can render precise diagnostics.
631fn wrap_module_error(
632    filename: &str,
633    e: Box<dyn std::error::Error>,
634) -> Box<dyn std::error::Error> {
635    if let Some(inner) = e.downcast_ref::<crate::Error>() {
636        let message = inner.get_field("message").unwrap_or_default().to_string();
637        let location = inner
638            .get_field("location")
639            .unwrap_or("<module>")
640            .replace("<module>", filename);
641        let help = inner.get_field("help").unwrap_or_default().to_string();
642        return Box::from(crate::codegen_error(
643            crate::SourceLocation::new(location),
644            message,
645            help,
646        ));
647    }
648    Box::from(crate::codegen_error(
649        crate::SourceLocation::new(filename),
650        crate::format_error_chain(e.as_ref()),
651        "",
652    ))
653}
654
655impl Module {
656    /// Check if the __name__ == "__main__" block contains only a simple call to main()
657    /// This includes patterns like:
658    /// - main()
659    /// - result = main()
660    /// - sys.exit(main())
661    fn is_simple_main_call_block(body: &[crate::Statement]) -> bool {
662        // Must have exactly one statement
663        if body.len() != 1 {
664            return false;
665        }
666        
667        let stmt = &body[0];
668        match &stmt.statement {
669            // Pattern 1: main() - direct call as expression statement
670            crate::StatementType::Expr(expr) => {
671                Self::is_main_function_call(&expr.value)
672            },
673            // Pattern 2: result = main() - assignment from main call
674            crate::StatementType::Assign(assign) => {
675                // Should have exactly one target and the value should be a main() call
676                assign.targets.len() == 1 && Self::is_main_function_call(&assign.value)
677            },
678            // Pattern 3: sys.exit(main()) - call with main() as argument
679            crate::StatementType::Call(call) => {
680                // Check if any of the arguments is a main() call
681                call.args.iter().any(|arg| Self::is_main_function_call(arg))
682            },
683            _ => false,
684        }
685    }
686    
687    /// Check if an expression is a call to a function named "main"
688    fn is_main_function_call(expr: &crate::ExprType) -> bool {
689        match expr {
690            crate::ExprType::Call(call) => {
691                match call.func.as_ref() {
692                    crate::ExprType::Name(name) => name.id == "main",
693                    _ => false,
694                }
695            },
696            _ => false,
697        }
698    }
699    
700    /// Determine if a statement is a declaration (can stay at module level) or executable code (needs to go in init function)
701    fn is_declaration_statement(stmt_type: &crate::StatementType) -> bool {
702        use crate::StatementType::*;
703        match stmt_type {
704            // These are declarations that can stay at module level
705            FunctionDef(_) | AsyncFunctionDef(_) | ClassDef(_) | Import(_) | ImportFrom(_) => true,
706            
707            // Standalone expressions can stay at module level (e.g., constants, simple values)
708            // These are typically used in tests or simple modules
709            Expr(expr) => Self::is_simple_expression(&expr.value),
710            
711            // These are executable statements that must go in the init function
712            Assign(_) | AugAssign(_) | Call(_) | Return(_) |
713            If(_) | For(_) | While(_) | Try(_) | With(_) | AsyncWith(_) | AsyncFor(_) |
714            Raise(_) | Assert { .. } | Pass | Break | Continue => false,
715            
716            // Handle unimplemented statements conservatively as executable
717            Unimplemented(_) => false,
718        }
719    }
720    
721    /// Check if an expression is simple enough to remain at module level
722    fn is_simple_expression(expr: &crate::ExprType) -> bool {
723        use crate::ExprType::*;
724        match expr {
725            // Simple constants and literals can stay at module level
726            Constant(_) | Name(_) | NoneType(_) => true,
727            
728            // Allow unary operations for single-expression modules (test compatibility)
729            UnaryOp(_) => true,
730            
731            // Function calls and complex expressions should go in init
732            Call(_) | BinOp(_) | Compare(_) | BoolOp(_) | 
733            IfExp(_) | Dict(_) | Set(_) | List(_) | Tuple(_) | ListComp(_) |
734            Lambda(_) | Attribute(_) | Subscript(_) | Starred(_) |
735            DictComp(_) | SetComp(_) | GeneratorExp(_) | Await(_) | 
736            Yield(_) | YieldFrom(_) | FormattedValue(_) | JoinedStr(_) |
737            NamedExpr(_) => false,
738            
739            // Be conservative about other expression types
740            Unimplemented(_) | Unknown => false,
741        }
742    }
743    
744    /// Rename the main function definition and update all references to it throughout the code
745    fn rename_main_function_and_references(code: &str) -> String {
746        // First, rename the function definitions
747        let code = code
748            .replace("pub async fn main (", "pub async fn python_main (")
749            .replace("pub fn main (", "pub fn python_main (");
750        
751        // Then update all references using the comprehensive reference updater
752        Self::update_main_references(&code)
753    }
754    
755    /// Convert a Python main function to be suitable as a Rust entry point
756    /// This handles return value conversion and signature requirements
757    fn convert_python_main_to_rust_entry_point(code: &str) -> String {
758        use regex::Regex;
759        
760        // Replace "pub fn main (" with "fn main("
761        let code = code.replace("pub fn main (", "fn main(");
762        
763        // Handle return statements in the main function
764        // We need to wrap the function body to ignore return values
765        let main_fn_pattern = Regex::new(r"fn main\(\s*\)\s*\{([^}]*)\}").unwrap();
766        
767        if let Some(captures) = main_fn_pattern.captures(&code) {
768            let body = captures.get(1).map_or("", |m| m.as_str());
769            
770            // Check if the body contains return statements
771            if body.contains("return ") {
772                // Wrap the original function as python_main and create new main that ignores return
773                let new_code = code.replace("fn main(", "fn python_main(");
774                format!("{}\n\nfn main() {{\n    let _ = python_main();\n}}", new_code)
775            } else {
776                // No return statements, use the function as-is
777                code
778            }
779        } else {
780            // Couldn't parse the function, fall back to original
781            code
782        }
783    }
784    
785    /// Update all references to main() function calls with python_main() calls
786    /// This uses regex to handle various call patterns with parameters
787    fn update_main_references(code: &str) -> String {
788        use regex::Regex;
789        
790        // Pattern 1: main(...) - function calls with any arguments (including empty)
791        // This pattern matches "main(" and lets us replace the function name
792        let call_pattern = Regex::new(r"\bmain\s*\(").unwrap();
793        let mut result = call_pattern.replace_all(code, "python_main(").to_string();
794        
795        // Pattern 2: Handle method calls like obj.call_main() -> obj.call_python_main()
796        let method_pattern = Regex::new(r"\.call_main\s*\(").unwrap();
797        result = method_pattern.replace_all(&result, ".call_python_main(").to_string();
798        
799        // Pattern 3: Handle assignment patterns like "result = main" (without parentheses)
800        // We need to be careful not to match function definitions or other contexts
801        let assignment_pattern = Regex::new(r"=\s+main\b").unwrap();
802        result = assignment_pattern.replace_all(&result, "= python_main").to_string();
803        
804        // Pattern 4: Handle return statements like "return main"
805        let return_pattern = Regex::new(r"return\s+main\b").unwrap();
806        result = return_pattern.replace_all(&result, "return python_main").to_string();
807        
808        result
809    }
810    
811    fn get_module_docstring(&self) -> Option<String> {
812        if self.raw.body.is_empty() {
813            return None;
814        }
815        
816        // Check if the first statement is a string constant (docstring)
817        let first_stmt = &self.raw.body[0];
818        match &first_stmt.statement {
819            StatementType::Expr(expr) => match &expr.value {
820                ExprType::Constant(c) => {
821                    let raw_string = c.to_string();
822                    Some(self.format_module_docstring(&raw_string))
823                },
824                _ => None,
825            },
826            _ => None,
827        }
828    }
829    
830    fn format_module_docstring(&self, raw: &str) -> String {
831        // Remove surrounding quotes
832        let content = raw.trim_matches('"');
833        
834        // Split into lines and clean up Python-style indentation
835        let lines: Vec<&str> = content.lines().collect();
836        if lines.is_empty() {
837            return String::new();
838        }
839        
840        // For module docstrings, preserve more of the original formatting
841        let mut formatted = Vec::new();
842        
843        for line in lines {
844            let cleaned = line.trim();
845            if !cleaned.is_empty() {
846                formatted.push(cleaned.to_string());
847            } else {
848                formatted.push(String::new());
849            }
850        }
851        
852        formatted.join("\n")
853    }
854    
855    fn looks_like_module_docstring(&self) -> bool {
856        if self.raw.body.is_empty() {
857            return false;
858        }
859        
860        // Check if the first statement looks like a module docstring
861        let first_stmt = &self.raw.body[0];
862        if let StatementType::Expr(expr) = &first_stmt.statement {
863            if let ExprType::Constant(c) = &expr.value {
864                let raw_string = c.to_string();
865                let content = raw_string.trim_matches('"');
866                
867                // Heuristics to detect if this is a module docstring vs just a string expression:
868                // 1. Contains multiple lines
869                // 2. Contains common docstring keywords
870                // 3. Looks like documentation rather than a simple string
871                return content.lines().count() > 1 
872                    || content.to_lowercase().contains("module")
873                    || content.to_lowercase().contains("this ")
874                    || content.len() > 50; // Longer strings are more likely to be docstrings
875            }
876        }
877        false
878    }
879}
880
881impl Object for Module {
882    /// __dir__ is called to list the attributes of the object.
883    fn __dir__(&self) -> Vec<impl AsRef<str>> {
884        // XXX - Make this meaningful.
885        vec![
886            "__class__",
887            "__class_getitem__",
888            "__contains__",
889            "__delattr__",
890            "__delitem__",
891            "__dir__",
892            "__doc__",
893            "__eq__",
894            "__format__",
895            "__ge__",
896            "__getattribute__",
897            "__getitem__",
898            "__getstate__",
899            "__gt__",
900            "__hash__",
901            "__init__",
902            "__init_subclass__",
903            "__ior__",
904            "__iter__",
905            "__le__",
906            "__len__",
907            "__lt__",
908            "__ne__",
909            "__new__",
910            "__or__",
911            "__reduce__",
912            "__reduce_ex__",
913            "__repr__",
914            "__reversed__",
915            "__ror__",
916            "__setattr__",
917            "__setitem__",
918            "__sizeof__",
919            "__str__",
920            "__subclasshook__",
921            "clear",
922            "copy",
923            "fromkeys",
924            "get",
925            "items",
926            "keys",
927            "pop",
928            "popitem",
929            "setdefault",
930            "update",
931            "values",
932        ]
933    }
934}
935
936#[cfg(test)]
937mod tests {
938    use super::*;
939
940    #[test]
941    fn can_we_print() {
942        let options = PythonOptions::default();
943        let result = crate::parse(
944            "#test comment
945def foo():
946    print(\"Test print.\")
947",
948            "test_case.py",
949        )
950        .unwrap();
951        info!("Python tree: {:?}", result);
952        //info!("{}", result);
953
954        let code = result.to_rust(
955            CodeGenContext::Module("test_case".to_string()),
956            options,
957            SymbolTableScopes::new(),
958        );
959        info!("module: {:?}", code);
960    }
961
962    #[test]
963    fn can_we_import() {
964        let result = crate::parse("import ast", "ast.py").unwrap();
965        let options = PythonOptions::default();
966        info!("{:?}", result);
967
968        let code = result.to_rust(
969            CodeGenContext::Module("test_case".to_string()),
970            options,
971            SymbolTableScopes::new(),
972        );
973        info!("module: {:?}", code);
974    }
975
976    #[test]
977    fn can_we_import2() {
978        let result = crate::parse("import ast as test", "ast.py").unwrap();
979        let options = PythonOptions::default();
980        info!("{:?}", result);
981
982        let code = result.to_rust(
983            CodeGenContext::Module("test_case".to_string()),
984            options,
985            SymbolTableScopes::new(),
986        );
987        info!("module: {:?}", code);
988    }
989}