1use crate::hir::*;
7use crate::lifetime_analysis::LifetimeInference;
8use crate::rust_gen::context::{CodeGenContext, RustCodeGen};
9use crate::rust_gen::control_flow_analysis::stmt_always_returns;
10use crate::rust_gen::func_gen::{
11 build_var_type_env, codegen_function_body, codegen_function_params,
12 collect_return_types_with_env, function_returns_owned_string,
13 function_returns_string_concatenation, infer_expr_type_with_env, infer_param_type_from_body,
14 infer_return_type_from_body_with_params, rewrite_adt_child_type,
15};
16use crate::rust_gen::func_gen_helpers::{
17 codegen_function_attrs, codegen_generic_params, codegen_where_clause,
18};
19use crate::rust_gen::generator_gen::codegen_generator_function;
20use crate::rust_gen::keywords::is_rust_keyword;
21use crate::rust_gen::rust_type_to_syn;
22use crate::rust_gen::type_gen::update_import_needs;
23use anyhow::Result;
24use quote::quote;
25use syn::parse_quote;
26
27pub(crate) fn detect_returns_nested_function(
31 func: &HirFunction,
32 ctx: &mut CodeGenContext,
33) -> Option<(String, Vec<HirParam>, Type)> {
34 let mut nested_functions: std::collections::HashMap<String, (Vec<HirParam>, Type)> =
36 std::collections::HashMap::new();
37
38 for stmt in &func.body {
40 if let HirStmt::FunctionDef {
41 name,
42 params,
43 ret_type,
44 body,
45 ..
46 } = stmt
47 {
48 let mut inferred_params = params.to_vec();
51 for param in &mut inferred_params {
52 if matches!(param.ty, Type::Unknown) {
53 if let Some(inferred_ty) = infer_param_type_from_body(¶m.name, body) {
55 param.ty = inferred_ty;
56 }
57 } else if let Type::Optional(inner) = ¶m.ty {
58 if matches!(inner.as_ref(), Type::Unknown) {
60 if let Some(inferred_ty) = infer_param_type_from_body(¶m.name, body) {
61 param.ty = Type::Optional(Box::new(inferred_ty));
62 }
63 }
64 }
65 }
66
67 let inferred_ret_type = if matches!(ret_type, Type::Unknown) {
71 let mut var_types: std::collections::HashMap<String, Type> =
73 std::collections::HashMap::new();
74 for p in &inferred_params {
75 var_types.insert(p.name.clone(), p.ty.clone());
76 }
77 build_var_type_env(body, &mut var_types);
79
80 let mut return_types = Vec::new();
82 collect_return_types_with_env(body, &mut return_types, &var_types);
83
84 if let Some(HirStmt::Expr(expr)) = body.last() {
86 let trailing_type = infer_expr_type_with_env(expr, &var_types);
87 if !matches!(trailing_type, Type::Unknown) {
88 return_types.push(trailing_type);
89 }
90 }
91
92 return_types
94 .iter()
95 .find(|t| !matches!(t, Type::Unknown))
96 .cloned()
97 .unwrap_or_else(|| ret_type.clone())
98 } else {
99 ret_type.clone()
100 };
101
102 ctx.nested_function_params
104 .insert(name.clone(), inferred_params.clone());
105
106 nested_functions.insert(name.clone(), (inferred_params, inferred_ret_type));
107 }
108 }
109
110 if let Some(last_stmt) = func.body.last() {
112 if let HirStmt::Return(Some(HirExpr::Var(var_name))) = last_stmt {
114 if let Some((params, ret_type)) = nested_functions.get(var_name) {
115 return Some((var_name.clone(), params.clone(), ret_type.clone()));
116 }
117 }
118 if let HirStmt::Expr(HirExpr::Var(var_name)) = last_stmt {
120 if let Some((params, ret_type)) = nested_functions.get(var_name) {
121 return Some((var_name.clone(), params.clone(), ret_type.clone()));
122 }
123 }
124 }
125
126 None
127}
128
129pub(crate) fn function_returns_heterogeneous_io(func: &HirFunction) -> bool {
132 let mut has_file_return = false;
133 let mut has_stdio_return = false;
134
135 collect_io_return_types(&func.body, &mut has_file_return, &mut has_stdio_return);
136
137 has_file_return && has_stdio_return
138}
139
140pub(crate) fn collect_io_return_types(
142 stmts: &[HirStmt],
143 has_file: &mut bool,
144 has_stdio: &mut bool,
145) {
146 for stmt in stmts {
147 match stmt {
148 HirStmt::Return(Some(expr)) => {
149 if is_file_creating_return_expr(expr) {
150 *has_file = true;
151 }
152 if is_stdio_return_expr(expr) {
153 *has_stdio = true;
154 }
155 }
156 HirStmt::If {
157 then_body,
158 else_body,
159 ..
160 } => {
161 collect_io_return_types(then_body, has_file, has_stdio);
162 if let Some(else_stmts) = else_body {
163 collect_io_return_types(else_stmts, has_file, has_stdio);
164 }
165 }
166 HirStmt::While { body, .. } | HirStmt::For { body, .. } => {
167 collect_io_return_types(body, has_file, has_stdio);
168 }
169 _ => {}
170 }
171 }
172}
173
174pub(crate) fn is_file_creating_return_expr(expr: &HirExpr) -> bool {
176 match expr {
177 HirExpr::Call { func, .. } => func == "open",
178 HirExpr::MethodCall { object, method, .. } => {
179 if method == "create" || method == "open" {
180 if let HirExpr::Var(name) = object.as_ref() {
181 return name == "File";
182 }
183 if let HirExpr::Attribute { attr, .. } = object.as_ref() {
184 return attr == "File";
185 }
186 }
187 false
188 }
189 _ => false,
190 }
191}
192
193pub(crate) fn is_stdio_return_expr(expr: &HirExpr) -> bool {
195 if let HirExpr::Attribute { value, attr } = expr {
196 if attr == "stdout" || attr == "stderr" {
197 if let HirExpr::Var(name) = value.as_ref() {
198 return name == "sys";
199 }
200 }
201 }
202 false
203}
204
205#[inline]
210pub(crate) fn codegen_return_type(
211 func: &HirFunction,
212 lifetime_result: &crate::lifetime_analysis::LifetimeResult,
213 ctx: &mut CodeGenContext,
214) -> Result<(
215 proc_macro2::TokenStream,
216 crate::type_mapper::RustType,
217 bool,
218 Option<crate::rust_gen::context::ErrorType>,
219)> {
220 if let Some((_nested_name, params, nested_ret_type)) = detect_returns_nested_function(func, ctx)
222 {
223 use quote::quote;
224
225 let param_types: Vec<proc_macro2::TokenStream> = params
227 .iter()
228 .map(|p| crate::rust_gen::type_tokens::hir_type_to_tokens(&p.ty))
229 .collect();
230
231 let ret_ty_tokens = crate::rust_gen::type_tokens::hir_type_to_tokens(&nested_ret_type);
232
233 let fn_type = if params.is_empty() {
234 quote! { -> Box<dyn Fn() -> #ret_ty_tokens> }
235 } else {
236 quote! { -> Box<dyn Fn(#(#param_types),*) -> #ret_ty_tokens> }
237 };
238
239 return Ok((
240 fn_type.clone(),
241 crate::type_mapper::RustType::Custom("BoxedFn".to_string()),
242 false, None, ));
245 }
246
247 if function_returns_heterogeneous_io(func) {
250 use quote::quote;
251 ctx.function_returns_boxed_write = true;
252 ctx.needs_io_write = true;
253
254 let can_fail = func.properties.can_fail;
256 let error_type = if can_fail {
257 Some(crate::rust_gen::context::ErrorType::Concrete(
258 "std::io::Error".to_string(),
259 ))
260 } else {
261 None
262 };
263
264 let return_type = if can_fail {
265 quote! { -> Result<Box<dyn std::io::Write>, std::io::Error> }
266 } else {
267 quote! { -> Box<dyn std::io::Write> }
268 };
269
270 return Ok((
271 return_type,
272 crate::type_mapper::RustType::Custom("BoxedWrite".to_string()),
273 can_fail,
274 error_type,
275 ));
276 }
277
278 let should_infer = matches!(func.ret_type, Type::Unknown | Type::None)
289 || matches!(&func.ret_type, Type::Tuple(elems) if elems.is_empty() || elems.iter().any(|t| matches!(t, Type::Unknown)))
290 || matches!(&func.ret_type, Type::List(elem) if matches!(**elem, Type::Unknown | Type::UnificationVar(_)))
291 || matches!(&func.ret_type, Type::Custom(name) if name == "tuple");
292
293 let effective_ret_type = if should_infer {
294 infer_return_type_from_body_with_params(func, ctx).unwrap_or_else(|| func.ret_type.clone())
296 } else {
297 func.ret_type.clone()
298 };
299
300 if should_infer && effective_ret_type != func.ret_type {
304 ctx.function_return_types
305 .insert(func.name.clone(), effective_ret_type.clone());
306 }
307
308 let effective_ret_type = if !ctx.type_substitutions.is_empty() {
311 crate::generic_inference::TypeVarRegistry::apply_substitutions(
312 &effective_ret_type,
313 &ctx.type_substitutions,
314 )
315 } else {
316 effective_ret_type
317 };
318
319 let effective_ret_type = rewrite_adt_child_type(&effective_ret_type, &ctx.adt_child_to_parent);
323
324 let mapped_ret_type = ctx
326 .annotation_aware_mapper
327 .map_return_type_with_annotations(&effective_ret_type, &func.annotations);
328
329 let rust_ret_type = if let crate::type_mapper::RustType::Enum { name, .. } = &mapped_ret_type {
331 if name == "UnionType" {
332 if let Type::Union(types) = &func.ret_type {
334 let enum_name = ctx.process_union_type(types);
335 crate::type_mapper::RustType::Custom(enum_name)
336 } else {
337 mapped_ret_type
338 }
339 } else {
340 mapped_ret_type
341 }
342 } else {
343 mapped_ret_type
344 };
345
346 let rust_ret_type =
349 if matches!(func.ret_type, Type::String) && function_returns_owned_string(func) {
350 crate::type_mapper::RustType::String
352 } else {
353 rust_ret_type
354 };
355
356 update_import_needs(ctx, &rust_ret_type);
358
359 let can_fail = func.properties.can_fail;
361 let mut error_type_str = if can_fail && !func.properties.error_types.is_empty() {
362 if func.properties.error_types.len() == 1 {
364 func.properties.error_types[0].clone()
365 } else {
366 "Box<dyn std::error::Error>".to_string()
367 }
368 } else {
369 "Box<dyn std::error::Error>".to_string()
370 };
371
372 error_type_str = match error_type_str.as_str() {
376 "OSError" | "IOError" | "FileNotFoundError" | "PermissionError" => {
378 "std::io::Error".to_string()
379 }
380 "Exception"
382 | "BaseException"
383 | "ValueError"
384 | "TypeError"
385 | "KeyError"
386 | "IndexError"
387 | "RuntimeError"
388 | "AttributeError"
389 | "NotImplementedError"
390 | "AssertionError"
391 | "StopIteration"
392 | "ZeroDivisionError"
393 | "OverflowError"
394 | "ArithmeticError" => "Box<dyn std::error::Error>".to_string(),
395 _ => error_type_str,
397 };
398
399 if ctx.validator_functions.contains(&func.name) {
401 error_type_str = "Box<dyn std::error::Error>".to_string();
402 }
403
404 let error_type = if can_fail {
408 Some(if error_type_str.contains("Box<dyn") {
409 crate::rust_gen::context::ErrorType::DynBox
410 } else {
411 crate::rust_gen::context::ErrorType::Concrete(error_type_str.clone())
412 })
413 } else {
414 None
415 };
416
417 if error_type_str.contains("ZeroDivisionError") {
422 ctx.needs_zerodivisionerror = true;
423 }
424 if error_type_str.contains("IndexError") {
425 ctx.needs_indexerror = true;
426 }
427 if error_type_str.contains("ValueError") {
428 ctx.needs_valueerror = true;
429 }
430 if error_type_str.contains("RuntimeError") {
431 ctx.needs_runtimeerror = true;
432 }
433 if error_type_str.contains("FileNotFoundError") {
434 ctx.needs_filenotfounderror = true;
435 }
436
437 for err_type in &func.properties.error_types {
440 if err_type.contains("ZeroDivisionError") {
441 ctx.needs_zerodivisionerror = true;
442 }
443 if err_type.contains("IndexError") {
444 ctx.needs_indexerror = true;
445 }
446 if err_type.contains("ValueError") {
447 ctx.needs_valueerror = true;
448 }
449 if err_type.contains("RuntimeError") {
450 ctx.needs_runtimeerror = true;
451 }
452 if err_type.contains("FileNotFoundError") {
453 ctx.needs_filenotfounderror = true;
454 }
455 }
456
457 let return_type = if matches!(rust_ret_type, crate::type_mapper::RustType::Unit) {
458 if can_fail {
459 let error_type: syn::Type = syn::parse_str(&error_type_str)
460 .unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
461
462 if let Some(inferred_type) = infer_return_type_from_body_with_params(func, ctx) {
472 let inferred_rust_type = ctx
475 .annotation_aware_mapper
476 .map_return_type_with_annotations(&inferred_type, &func.annotations);
477
478 if let Ok(ty) = rust_type_to_syn(&inferred_rust_type) {
480 if func.name == "main" {
482 quote! { -> Result<(), #error_type> }
483 } else {
484 quote! { -> Result<#ty, #error_type> }
486 }
487 } else {
488 quote! { -> Result<(), #error_type> }
490 }
491 } else {
492 quote! { -> Result<(), #error_type> }
494 }
495 } else {
496 quote! {}
497 }
498 } else {
499 let mut ty = rust_type_to_syn(&rust_ret_type)?;
500
501 let returns_concatenation = matches!(func.ret_type, crate::hir::Type::String)
505 && function_returns_string_concatenation(func);
506
507 let mut uses_cow_return = false;
509 if !returns_concatenation {
510 for param in &func.params {
512 if let Some(strategy) = lifetime_result.borrowing_strategies.get(¶m.name) {
513 if matches!(
514 strategy,
515 crate::borrowing_context::BorrowingStrategy::UseCow { .. }
516 ) {
517 if let Some(_usage) = lifetime_result.param_lifetimes.get(¶m.name) {
518 if matches!(func.ret_type, crate::hir::Type::String) {
520 uses_cow_return = true;
521 break;
522 }
523 }
524 }
525 }
526 }
527 }
528
529 if uses_cow_return && !returns_concatenation {
530 ctx.needs_cow = true;
532 if let Some(ref return_lt) = lifetime_result.return_lifetime {
533 let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
534 ty = parse_quote! { Cow<#lt, str> };
535 } else {
536 ty = parse_quote! { Cow<'static, str> };
537 }
538 } else {
539 let returns_owned_string =
542 matches!(func.ret_type, Type::String) && function_returns_owned_string(func);
543
544 if let Some(ref return_lt) = lifetime_result.return_lifetime {
546 if matches!(
548 rust_ret_type,
549 crate::type_mapper::RustType::Str { .. }
550 | crate::type_mapper::RustType::Reference { .. }
551 ) && !returns_owned_string
552 {
553 let lt = syn::Lifetime::new(return_lt.as_str(), proc_macro2::Span::call_site());
555 match &rust_ret_type {
556 crate::type_mapper::RustType::Str { .. } => {
557 ty = parse_quote! { &#lt str };
558 }
559 crate::type_mapper::RustType::Reference { mutable, inner, .. } => {
560 let inner_ty = rust_type_to_syn(inner)?;
561 ty = if *mutable {
562 parse_quote! { &#lt mut #inner_ty }
563 } else {
564 parse_quote! { &#lt #inner_ty }
565 };
566 }
567 _ => {}
568 }
569 }
570 }
571 }
573
574 if can_fail {
575 let error_type: syn::Type = syn::parse_str(&error_type_str)
576 .unwrap_or_else(|_| parse_quote! { Box<dyn std::error::Error> });
577
578 if func.name == "main" {
581 quote! { -> Result<(), #error_type> }
582 } else {
583 quote! { -> Result<#ty, #error_type> }
584 }
585 } else if func.name == "main" && matches!(func.ret_type, Type::Int) {
586 quote! {} } else {
590 quote! { -> #ty }
591 }
592 };
593
594 Ok((return_type, rust_ret_type, can_fail, error_type))
595}
596
597pub(crate) fn preload_hir_type_annotations(body: &[HirStmt], ctx: &mut CodeGenContext) {
611 for stmt in body {
612 preload_stmt_type_annotations(stmt, ctx);
613 }
614}
615
616fn preload_stmt_type_annotations(stmt: &HirStmt, ctx: &mut CodeGenContext) {
618 match stmt {
619 HirStmt::Assign {
620 target: AssignTarget::Symbol(var_name),
621 type_annotation: Some(ty),
622 ..
623 } => {
624 if !matches!(ty, Type::Unknown) {
626 let should_overwrite = match ctx.var_types.get(var_name) {
631 None => true,
632 Some(Type::Unknown) => true,
633 Some(existing) => {
634 std::mem::discriminant(existing) == std::mem::discriminant(ty)
637 }
638 };
639 if should_overwrite {
640 ctx.var_types.insert(var_name.clone(), ty.clone());
641 }
642 }
643 }
644 HirStmt::If {
646 then_body,
647 else_body,
648 ..
649 } => {
650 for s in then_body {
651 preload_stmt_type_annotations(s, ctx);
652 }
653 if let Some(else_stmts) = else_body {
654 for s in else_stmts {
655 preload_stmt_type_annotations(s, ctx);
656 }
657 }
658 }
659 HirStmt::While { body, .. } | HirStmt::Block(body) => {
660 for s in body {
661 preload_stmt_type_annotations(s, ctx);
662 }
663 }
664 HirStmt::For { body, .. } => {
665 for s in body {
666 preload_stmt_type_annotations(s, ctx);
667 }
668 }
669 HirStmt::Try {
670 body,
671 handlers,
672 orelse,
673 finalbody,
674 } => {
675 for s in body {
676 preload_stmt_type_annotations(s, ctx);
677 }
678 for handler in handlers {
679 for s in &handler.body {
680 preload_stmt_type_annotations(s, ctx);
681 }
682 }
683 if let Some(orelse_stmts) = orelse {
684 for s in orelse_stmts {
685 preload_stmt_type_annotations(s, ctx);
686 }
687 }
688 if let Some(final_stmts) = finalbody {
689 for s in final_stmts {
690 preload_stmt_type_annotations(s, ctx);
691 }
692 }
693 }
694 HirStmt::With { body, .. } => {
695 for s in body {
696 preload_stmt_type_annotations(s, ctx);
697 }
698 }
699 HirStmt::FunctionDef { body, .. } => {
700 for s in body {
703 preload_stmt_type_annotations(s, ctx);
704 }
705 }
706 _ => {}
707 }
708}
709
710impl RustCodeGen for HirFunction {
711 fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
712 ctx.var_types.clear();
716 ctx.type_substitutions.clear();
717 ctx.slice_params.clear();
719
720 let name = if is_rust_keyword(&self.name) {
722 syn::Ident::new_raw(&self.name, proc_macro2::Span::call_site())
723 } else {
724 syn::Ident::new(&self.name, proc_macro2::Span::call_site())
725 };
726
727 ctx.function_return_types
731 .insert(self.name.clone(), self.ret_type.clone());
732
733 let param_defaults: Vec<Option<crate::hir::HirExpr>> =
736 self.params.iter().map(|p| p.default.clone()).collect();
737 ctx.function_param_defaults
738 .insert(self.name.clone(), param_defaults);
739
740 let mut generic_registry = crate::generic_inference::TypeVarRegistry::new();
742
743 let type_substitutions = generic_registry.infer_type_substitutions(self)?;
745
746 if !type_substitutions.is_empty() {
749 for param in &self.params {
750 let substituted_ty = crate::generic_inference::TypeVarRegistry::apply_substitutions(
751 ¶m.ty,
752 &type_substitutions,
753 );
754 if substituted_ty != param.ty {
755 ctx.var_types.insert(param.name.clone(), substituted_ty);
756 }
757 }
758 ctx.type_substitutions = type_substitutions;
760 }
761
762 let mut inferred_params = self.params.clone();
768 for param in &mut inferred_params {
769 if matches!(param.ty, Type::Unknown) {
770 if let Some(inferred_ty) = infer_param_type_from_body(¶m.name, &self.body) {
771 param.ty = inferred_ty.clone();
772 ctx.var_types.insert(param.name.clone(), inferred_ty);
773 }
774 } else if let Type::Optional(inner) = ¶m.ty {
775 if matches!(inner.as_ref(), Type::Unknown) {
777 if let Some(inferred_ty) = infer_param_type_from_body(¶m.name, &self.body) {
778 let new_ty = Type::Optional(Box::new(inferred_ty));
779 param.ty = new_ty.clone();
780 ctx.var_types.insert(param.name.clone(), new_ty);
781 }
782 }
783 }
784 }
785
786 for param in &mut inferred_params {
789 if crate::container_element_inference::has_unknown_inner_type(¶m.ty) {
790 if let Some(refined) =
791 crate::container_element_inference::infer_container_element_type(
792 ¶m.name,
793 ¶m.ty,
794 &self.body,
795 )
796 {
797 param.ty = refined.clone();
798 ctx.var_types.insert(param.name.clone(), refined);
799 }
800 }
801 }
802
803 let inferred_self = HirFunction {
805 params: inferred_params,
806 ..self.clone()
807 };
808 let type_params = generic_registry.infer_function_generics(&inferred_self)?;
809
810 let mut lifetime_inference = LifetimeInference::new();
812 let lifetime_result = lifetime_inference
813 .apply_elision_rules(self, ctx.type_mapper)
814 .unwrap_or_else(|| lifetime_inference.analyze_function(self, ctx.type_mapper));
815
816 let generic_params = codegen_generic_params(&type_params, &lifetime_result.lifetime_params);
818
819 let where_clause = codegen_where_clause(&lifetime_result.lifetime_bounds);
821
822 crate::rust_gen::analyze_mutable_vars(&self.body, ctx, &self.params);
826
827 let params = codegen_function_params(self, &lifetime_result, ctx)?;
829
830 let param_borrows: Vec<bool> = self
833 .params
834 .iter()
835 .map(|p| {
836 lifetime_result
837 .param_lifetimes
838 .get(&p.name)
839 .map(|inf| inf.should_borrow)
840 .unwrap_or(false)
841 })
842 .collect();
843
844 ctx.ref_params.clear();
847 for (p, &is_borrowed) in self.params.iter().zip(param_borrows.iter()) {
848 if is_borrowed {
849 ctx.ref_params.insert(p.name.clone());
850 }
851 }
852
853 ctx.function_param_borrows
854 .insert(self.name.clone(), param_borrows);
855
856 let param_muts: Vec<bool> = self
859 .params
860 .iter()
861 .map(|p| {
862 let is_mutated = ctx.mutable_vars.contains(&p.name);
863 let should_borrow = lifetime_result
864 .param_lifetimes
865 .get(&p.name)
866 .map(|inf| inf.should_borrow)
867 .unwrap_or(false);
868 is_mutated && should_borrow
870 })
871 .collect();
872
873 ctx.mut_ref_params.clear();
876 for (p, &needs_mut) in self.params.iter().zip(param_muts.iter()) {
877 if needs_mut {
878 ctx.mut_ref_params.insert(p.name.clone());
879 }
880 }
881
882 ctx.function_param_muts
883 .insert(self.name.clone(), param_muts);
884
885 let param_optionals: Vec<bool> = self
888 .params
889 .iter()
890 .map(|p| {
891 let type_is_optional = matches!(p.ty, Type::Optional(_));
893 let default_is_none = matches!(p.default, Some(HirExpr::Literal(Literal::None)));
895 type_is_optional || default_is_none
896 })
897 .collect();
898 ctx.function_param_optionals
899 .insert(self.name.clone(), param_optionals);
900
901 if self.params.iter().any(|p| p.is_vararg) {
904 ctx.vararg_functions.insert(self.name.clone());
905 }
906
907 for param in &self.params {
914 let is_dict = matches!(¶m.ty, Type::Dict { .. })
915 || matches!(¶m.ty, Type::Custom(name) if name == "dict");
916 let has_none_default = matches!(¶m.default, Some(HirExpr::Literal(Literal::None)));
917 let is_optional_dict = matches!(
919 ¶m.ty,
920 Type::Optional(inner) if matches!(inner.as_ref(), Type::Dict { .. })
921 );
922 if (is_dict && has_none_default) || is_optional_dict {
923 ctx.mut_option_dict_params.insert(param.name.clone());
924 }
925
926 let is_optional = matches!(¶m.ty, Type::Optional(_))
930 || matches!(¶m.ty, Type::Union(types) if types.iter().any(|t| matches!(t, Type::None)));
931 let inferred_needs_mut = lifetime_result
933 .param_lifetimes
934 .get(¶m.name)
935 .map(|ip| ip.needs_mut)
936 .unwrap_or(false);
937
938 if (is_optional || has_none_default) && inferred_needs_mut {
940 ctx.mut_option_params.insert(param.name.clone());
941 }
942 }
943
944 let (return_type, rust_ret_type, can_fail, error_type) =
946 codegen_return_type(self, &lifetime_result, ctx)?;
947
948 let (generic_params, return_type, params) =
954 if let crate::type_mapper::RustType::Custom(ref type_str) = rust_ret_type {
955 if type_str.contains("impl Fn")
956 || type_str.contains("impl Iterator")
957 || type_str.contains("impl IntoIterator")
958 {
959 let has_ref_params = ctx
962 .function_param_borrows
963 .get(&self.name)
964 .map(|borrows| borrows.iter().any(|&b| b))
965 .unwrap_or(false);
966 if has_ref_params {
967 let mut lifetime_params_with_a = lifetime_result.lifetime_params.clone();
971 if !lifetime_params_with_a.contains(&"'a".to_string()) {
972 lifetime_params_with_a.push("'a".to_string());
973 }
974 lifetime_params_with_a.retain(|lt| lt == "'a");
976 let new_generic_params =
977 codegen_generic_params(&type_params, &lifetime_params_with_a);
978
979 let return_str = return_type.to_string();
984 let modified_return = if return_str.contains("impl Fn")
985 || return_str.contains("impl Iterator")
986 || return_str.contains("impl IntoIterator")
987 {
988 let modified = format!("{} + 'a", return_str.trim());
991 syn::parse_str::<proc_macro2::TokenStream>(&modified)
992 .unwrap_or(return_type.clone())
993 } else {
994 return_type.clone()
995 };
996
997 let modified_params: Vec<proc_macro2::TokenStream> = params
1002 .into_iter()
1003 .map(|p| {
1004 let param_str = p.to_string();
1005 let modified_param = param_str
1008 .replace("& 'b ", "& 'a ")
1009 .replace("& 'c ", "& 'a ")
1010 .replace("& 'd ", "& 'a ")
1011 .replace("& 'e ", "& 'a ");
1012 let modified_param = if modified_param.contains("& ")
1014 && !modified_param.contains("& '")
1015 {
1016 modified_param
1017 .replace("& mut ", "& 'a mut ")
1018 .replace("& Vec", "& 'a Vec")
1019 .replace("& str", "& 'a str")
1020 } else {
1021 modified_param
1022 };
1023 syn::parse_str::<proc_macro2::TokenStream>(&modified_param)
1024 .unwrap_or(p)
1025 })
1026 .collect();
1027
1028 (new_generic_params, modified_return, modified_params)
1029 } else {
1030 (generic_params.clone(), return_type, params)
1031 }
1032 } else {
1033 (generic_params.clone(), return_type, params)
1034 }
1035 } else {
1036 (generic_params.clone(), return_type, params)
1037 };
1038
1039 let subcommand_info = if ctx.argparser_tracker.has_subcommands() {
1042 crate::rust_gen::argparse_transform::analyze_subcommand_field_access(
1043 self,
1044 &ctx.argparser_tracker,
1045 )
1046 } else {
1047 None
1048 };
1049
1050 if let Some((_, ref fields)) = subcommand_info {
1052 ctx.current_subcommand_fields = Some(fields.iter().cloned().collect());
1053 }
1054
1055 crate::rust_gen::argparse_transform::preregister_subcommands_from_hir(
1059 self,
1060 &mut ctx.argparser_tracker,
1061 );
1062
1063 if ctx.argparser_tracker.has_parsers() {
1067 if let Some(parser_info) = ctx.argparser_tracker.get_first_parser() {
1068 for arg in &parser_info.arguments {
1069 if arg.rust_type().starts_with("Option<") {
1070 ctx.precomputed_option_fields
1071 .insert(arg.rust_field_name().to_string());
1072 }
1073 }
1074 }
1075 }
1076
1077 let was_main = ctx.is_main_function;
1080 ctx.is_main_function = self.name == "main";
1081
1082 for param in &self.params {
1087 if !matches!(param.ty, Type::Unknown) {
1088 ctx.var_types.insert(param.name.clone(), param.ty.clone());
1089 }
1090 }
1091
1092 preload_hir_type_annotations(&self.body, ctx);
1099
1100 let mut body_stmts = codegen_function_body(self, can_fail, error_type, ctx)?;
1102
1103 {
1107 use quote::quote;
1108 let body_is_empty = body_stmts.iter().all(|stmt| stmt.is_empty());
1109 let is_non_unit_return = !matches!(rust_ret_type, crate::type_mapper::RustType::Unit);
1110 if body_is_empty && is_non_unit_return {
1111 body_stmts.push(quote! { unimplemented!() });
1112 }
1113 }
1114
1115 if matches!(rust_ret_type, crate::type_mapper::RustType::Unit) {
1120 if let Some(last) = body_stmts.last_mut() {
1121 let last_str = last.to_string();
1122 if !last_str.is_empty()
1126 && !last_str.trim_end().ends_with(';')
1127 && !last_str.trim_end().ends_with('}')
1128 {
1129 use quote::quote;
1130 let tokens = std::mem::take(last);
1131 *last = quote! { let _ = #tokens; };
1134 }
1135 }
1136 }
1137
1138 ctx.is_main_function = was_main;
1140
1141 if let Some((nested_name, _, _)) = detect_returns_nested_function(self, ctx) {
1143 if let Some(last_stmt) = body_stmts.last_mut() {
1145 use quote::quote;
1146 let nested_ident = syn::Ident::new(&nested_name, proc_macro2::Span::call_site());
1147 let last_stmt_str = last_stmt.to_string();
1149 if last_stmt_str.trim() == nested_name {
1150 *last_stmt = quote! { Box::new(#nested_ident) };
1152 }
1153 }
1154 }
1155
1156 ctx.current_subcommand_fields = None;
1158
1159 if ctx.argparser_tracker.has_parsers() {
1163 if let Some(parser_info) = ctx.argparser_tracker.get_first_parser() {
1164 ctx.needs_clap = true;
1166
1167 let commands_enum = crate::rust_gen::argparse_transform::generate_commands_enum(
1169 &ctx.argparser_tracker,
1170 );
1171 if !commands_enum.is_empty() {
1172 ctx.generated_commands_enum = Some(commands_enum);
1173 }
1174
1175 let args_struct = crate::rust_gen::argparse_transform::generate_args_struct(
1177 parser_info,
1178 &ctx.argparser_tracker,
1179 );
1180 ctx.generated_args_struct = Some(args_struct);
1181
1182 let precompute_stmts =
1185 crate::rust_gen::argparse_transform::generate_option_precompute(parser_info);
1186 if !precompute_stmts.is_empty() {
1187 let option_fields: Vec<String> = parser_info
1190 .arguments
1191 .iter()
1192 .filter(|arg| arg.rust_type().starts_with("Option<"))
1193 .map(|arg| arg.rust_field_name().to_string())
1194 .collect();
1195
1196 if !option_fields.is_empty() {
1197 body_stmts = body_stmts
1198 .into_iter()
1199 .map(|stmt| {
1200 let mut stmt_str = stmt.to_string();
1201 for field in &option_fields {
1202 let pattern = format!("args . {} . is_some ()", field);
1204 let replacement = format!("has_{}", field);
1205 stmt_str = stmt_str.replace(&pattern, &replacement);
1206 let pattern_none = format!("args . {} . is_none ()", field);
1208 let replacement_none = format!("! has_{}", field);
1209 stmt_str = stmt_str.replace(&pattern_none, &replacement_none);
1210 }
1211 syn::parse_str(&stmt_str).unwrap_or(stmt)
1212 })
1213 .collect();
1214 }
1215
1216 let insert_idx = body_stmts
1220 .iter()
1221 .position(|s| s.to_string().contains("Args :: parse"))
1222 .map(|i| i + 1)
1223 .unwrap_or(0);
1224 for (offset, stmt) in precompute_stmts.into_iter().enumerate() {
1225 body_stmts.insert(insert_idx + offset, stmt);
1226 }
1227 }
1228
1229 }
1232
1233 }
1236
1237 if let Some((variant_name, fields)) = subcommand_info {
1243 if !ctx.in_cmd_handler {
1244 if let Some(args_param) = self.params.first() {
1246 let args_param_name = args_param.name.as_ref();
1247 body_stmts =
1249 crate::rust_gen::argparse_transform::wrap_body_with_subcommand_pattern(
1250 body_stmts,
1251 &variant_name,
1252 &fields,
1253 args_param_name,
1254 );
1255 }
1256 }
1257 }
1258
1259 if can_fail {
1272 let needs_ok = self
1273 .body
1274 .last()
1275 .is_none_or(|stmt| !stmt_always_returns(stmt));
1276 if needs_ok {
1277 if matches!(self.ret_type, Type::None | Type::Unknown) {
1280 body_stmts.push(parse_quote! { Ok(()) });
1281 }
1282 }
1283 }
1284
1285 let attrs = codegen_function_attrs(
1287 &self.docstring,
1288 &self.properties,
1289 &self.annotations.custom_attributes,
1290 );
1291
1292 let func_tokens = if self.properties.is_generator {
1294 codegen_generator_function(
1295 self,
1296 &name,
1297 &generic_params,
1298 &where_clause,
1299 ¶ms,
1300 &attrs,
1301 &rust_ret_type,
1302 ctx,
1303 )?
1304 } else if self.properties.is_async {
1305 let nasa_mode = ctx.type_mapper.nasa_mode;
1307 if nasa_mode {
1308 quote! {
1311 #(#attrs)*
1312 pub fn #name #generic_params(#(#params),*) #return_type #where_clause {
1313 #(#body_stmts)*
1314 }
1315 }
1316 } else if self.name == "main" {
1317 ctx.needs_tokio = true;
1319 quote! {
1320 #(#attrs)*
1321 #[tokio::main]
1322 pub async fn #name #generic_params(#(#params),*) #return_type #where_clause {
1323 #(#body_stmts)*
1324 }
1325 }
1326 } else {
1327 quote! {
1328 #(#attrs)*
1329 pub async fn #name #generic_params(#(#params),*) #return_type #where_clause {
1330 #(#body_stmts)*
1331 }
1332 }
1333 }
1334 } else {
1335 quote! {
1336 #(#attrs)*
1337 pub fn #name #generic_params(#(#params),*) #return_type #where_clause {
1338 #(#body_stmts)*
1339 }
1340 }
1341 };
1342
1343 Ok(func_tokens)
1344 }
1345}
1346
1347#[cfg(test)]
1348mod tests {
1349 use super::*;
1350 use crate::hir::*;
1351
1352 #[test]
1355 fn test_is_file_creating_open_call() {
1356 let expr = HirExpr::Call {
1357 func: "open".to_string(),
1358 args: vec![HirExpr::Literal(Literal::String("test.txt".to_string()))],
1359 kwargs: vec![],
1360 };
1361 assert!(is_file_creating_return_expr(&expr));
1362 }
1363
1364 #[test]
1365 fn test_is_file_creating_other_call() {
1366 let expr = HirExpr::Call {
1367 func: "read".to_string(),
1368 args: vec![],
1369 kwargs: vec![],
1370 };
1371 assert!(!is_file_creating_return_expr(&expr));
1372 }
1373
1374 #[test]
1375 fn test_is_file_creating_file_create_method() {
1376 let expr = HirExpr::MethodCall {
1377 object: Box::new(HirExpr::Var("File".to_string())),
1378 method: "create".to_string(),
1379 args: vec![HirExpr::Literal(Literal::String("out.txt".to_string()))],
1380 kwargs: vec![],
1381 };
1382 assert!(is_file_creating_return_expr(&expr));
1383 }
1384
1385 #[test]
1386 fn test_is_file_creating_file_open_method() {
1387 let expr = HirExpr::MethodCall {
1388 object: Box::new(HirExpr::Var("File".to_string())),
1389 method: "open".to_string(),
1390 args: vec![HirExpr::Literal(Literal::String("in.txt".to_string()))],
1391 kwargs: vec![],
1392 };
1393 assert!(is_file_creating_return_expr(&expr));
1394 }
1395
1396 #[test]
1397 fn test_is_file_creating_attribute_file() {
1398 let expr = HirExpr::MethodCall {
1399 object: Box::new(HirExpr::Attribute {
1400 value: Box::new(HirExpr::Var("io".to_string())),
1401 attr: "File".to_string(),
1402 }),
1403 method: "create".to_string(),
1404 args: vec![],
1405 kwargs: vec![],
1406 };
1407 assert!(is_file_creating_return_expr(&expr));
1408 }
1409
1410 #[test]
1411 fn test_is_file_creating_non_file_method() {
1412 let expr = HirExpr::MethodCall {
1413 object: Box::new(HirExpr::Var("List".to_string())),
1414 method: "create".to_string(),
1415 args: vec![],
1416 kwargs: vec![],
1417 };
1418 assert!(!is_file_creating_return_expr(&expr));
1419 }
1420
1421 #[test]
1422 fn test_is_file_creating_non_create_method() {
1423 let expr = HirExpr::MethodCall {
1424 object: Box::new(HirExpr::Var("File".to_string())),
1425 method: "read".to_string(),
1426 args: vec![],
1427 kwargs: vec![],
1428 };
1429 assert!(!is_file_creating_return_expr(&expr));
1430 }
1431
1432 #[test]
1433 fn test_is_file_creating_var_expr() {
1434 let expr = HirExpr::Var("f".to_string());
1435 assert!(!is_file_creating_return_expr(&expr));
1436 }
1437
1438 #[test]
1441 fn test_is_stdio_stdout() {
1442 let expr = HirExpr::Attribute {
1443 value: Box::new(HirExpr::Var("sys".to_string())),
1444 attr: "stdout".to_string(),
1445 };
1446 assert!(is_stdio_return_expr(&expr));
1447 }
1448
1449 #[test]
1450 fn test_is_stdio_stderr() {
1451 let expr = HirExpr::Attribute {
1452 value: Box::new(HirExpr::Var("sys".to_string())),
1453 attr: "stderr".to_string(),
1454 };
1455 assert!(is_stdio_return_expr(&expr));
1456 }
1457
1458 #[test]
1459 fn test_is_stdio_not_sys() {
1460 let expr = HirExpr::Attribute {
1461 value: Box::new(HirExpr::Var("os".to_string())),
1462 attr: "stdout".to_string(),
1463 };
1464 assert!(!is_stdio_return_expr(&expr));
1465 }
1466
1467 #[test]
1468 fn test_is_stdio_not_stdout() {
1469 let expr = HirExpr::Attribute {
1470 value: Box::new(HirExpr::Var("sys".to_string())),
1471 attr: "path".to_string(),
1472 };
1473 assert!(!is_stdio_return_expr(&expr));
1474 }
1475
1476 #[test]
1477 fn test_is_stdio_plain_var() {
1478 let expr = HirExpr::Var("stdout".to_string());
1479 assert!(!is_stdio_return_expr(&expr));
1480 }
1481
1482 #[test]
1485 fn test_collect_io_return_types_empty() {
1486 let mut has_file = false;
1487 let mut has_stdio = false;
1488 collect_io_return_types(&[], &mut has_file, &mut has_stdio);
1489 assert!(!has_file);
1490 assert!(!has_stdio);
1491 }
1492
1493 #[test]
1494 fn test_collect_io_return_file() {
1495 let stmts = vec![HirStmt::Return(Some(HirExpr::Call {
1496 func: "open".to_string(),
1497 args: vec![HirExpr::Literal(Literal::String("f.txt".to_string()))],
1498 kwargs: vec![],
1499 }))];
1500 let mut has_file = false;
1501 let mut has_stdio = false;
1502 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1503 assert!(has_file);
1504 assert!(!has_stdio);
1505 }
1506
1507 #[test]
1508 fn test_collect_io_return_stdio() {
1509 let stmts = vec![HirStmt::Return(Some(HirExpr::Attribute {
1510 value: Box::new(HirExpr::Var("sys".to_string())),
1511 attr: "stdout".to_string(),
1512 }))];
1513 let mut has_file = false;
1514 let mut has_stdio = false;
1515 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1516 assert!(!has_file);
1517 assert!(has_stdio);
1518 }
1519
1520 #[test]
1521 fn test_collect_io_return_types_in_if_branch() {
1522 let stmts = vec![HirStmt::If {
1523 condition: HirExpr::Var("flag".to_string()),
1524 then_body: vec![HirStmt::Return(Some(HirExpr::Call {
1525 func: "open".to_string(),
1526 args: vec![],
1527 kwargs: vec![],
1528 }))],
1529 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Attribute {
1530 value: Box::new(HirExpr::Var("sys".to_string())),
1531 attr: "stderr".to_string(),
1532 }))]),
1533 }];
1534 let mut has_file = false;
1535 let mut has_stdio = false;
1536 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1537 assert!(has_file);
1538 assert!(has_stdio);
1539 }
1540
1541 #[test]
1542 fn test_collect_io_return_types_in_loop() {
1543 let stmts = vec![HirStmt::While {
1544 condition: HirExpr::Literal(Literal::Bool(true)),
1545 body: vec![HirStmt::Return(Some(HirExpr::Call {
1546 func: "open".to_string(),
1547 args: vec![],
1548 kwargs: vec![],
1549 }))],
1550 }];
1551 let mut has_file = false;
1552 let mut has_stdio = false;
1553 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1554 assert!(has_file);
1555 }
1556
1557 #[test]
1558 fn test_collect_io_return_types_in_for() {
1559 let stmts = vec![HirStmt::For {
1560 target: AssignTarget::Symbol("x".to_string()),
1561 iter: HirExpr::Call {
1562 func: "range".to_string(),
1563 args: vec![HirExpr::Literal(Literal::Int(10))],
1564 kwargs: vec![],
1565 },
1566 body: vec![HirStmt::Return(Some(HirExpr::Attribute {
1567 value: Box::new(HirExpr::Var("sys".to_string())),
1568 attr: "stdout".to_string(),
1569 }))],
1570 }];
1571 let mut has_file = false;
1572 let mut has_stdio = false;
1573 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1574 assert!(has_stdio);
1575 }
1576
1577 #[test]
1578 fn test_collect_io_return_none() {
1579 let stmts = vec![HirStmt::Return(None)];
1580 let mut has_file = false;
1581 let mut has_stdio = false;
1582 collect_io_return_types(&stmts, &mut has_file, &mut has_stdio);
1583 assert!(!has_file);
1584 assert!(!has_stdio);
1585 }
1586
1587 #[test]
1590 fn test_heterogeneous_io_both_types() {
1591 let func = HirFunction {
1592 name: "get_output".to_string(),
1593 params: smallvec::smallvec![HirParam::new("use_file".to_string(), Type::Bool)],
1594 ret_type: Type::Unknown,
1595 body: vec![HirStmt::If {
1596 condition: HirExpr::Var("use_file".to_string()),
1597 then_body: vec![HirStmt::Return(Some(HirExpr::Call {
1598 func: "open".to_string(),
1599 args: vec![],
1600 kwargs: vec![],
1601 }))],
1602 else_body: Some(vec![HirStmt::Return(Some(HirExpr::Attribute {
1603 value: Box::new(HirExpr::Var("sys".to_string())),
1604 attr: "stdout".to_string(),
1605 }))]),
1606 }],
1607 properties: FunctionProperties::default(),
1608 annotations: depyler_annotations::TranspilationAnnotations::default(),
1609 docstring: None,
1610 };
1611 assert!(function_returns_heterogeneous_io(&func));
1612 }
1613
1614 #[test]
1615 fn test_heterogeneous_io_only_file() {
1616 let func = HirFunction {
1617 name: "get_file".to_string(),
1618 params: smallvec::smallvec![],
1619 ret_type: Type::Unknown,
1620 body: vec![HirStmt::Return(Some(HirExpr::Call {
1621 func: "open".to_string(),
1622 args: vec![],
1623 kwargs: vec![],
1624 }))],
1625 properties: FunctionProperties::default(),
1626 annotations: depyler_annotations::TranspilationAnnotations::default(),
1627 docstring: None,
1628 };
1629 assert!(!function_returns_heterogeneous_io(&func));
1630 }
1631
1632 #[test]
1633 fn test_heterogeneous_io_neither() {
1634 let func = HirFunction {
1635 name: "get_val".to_string(),
1636 params: smallvec::smallvec![],
1637 ret_type: Type::Int,
1638 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
1639 properties: FunctionProperties::default(),
1640 annotations: depyler_annotations::TranspilationAnnotations::default(),
1641 docstring: None,
1642 };
1643 assert!(!function_returns_heterogeneous_io(&func));
1644 }
1645
1646 #[test]
1649 fn test_preload_simple_assign() {
1650 let mut ctx = CodeGenContext::default();
1651 let body = vec![HirStmt::Assign {
1652 target: AssignTarget::Symbol("x".to_string()),
1653 value: HirExpr::Literal(Literal::Int(42)),
1654 type_annotation: Some(Type::Int),
1655 }];
1656 preload_hir_type_annotations(&body, &mut ctx);
1657 assert_eq!(ctx.var_types.get("x"), Some(&Type::Int));
1658 }
1659
1660 #[test]
1661 fn test_preload_skips_unknown_type() {
1662 let mut ctx = CodeGenContext::default();
1663 let body = vec![HirStmt::Assign {
1664 target: AssignTarget::Symbol("x".to_string()),
1665 value: HirExpr::Literal(Literal::Int(1)),
1666 type_annotation: Some(Type::Unknown),
1667 }];
1668 preload_hir_type_annotations(&body, &mut ctx);
1669 assert!(ctx.var_types.get("x").is_none());
1670 }
1671
1672 #[test]
1673 fn test_preload_no_annotation() {
1674 let mut ctx = CodeGenContext::default();
1675 let body = vec![HirStmt::Assign {
1676 target: AssignTarget::Symbol("x".to_string()),
1677 value: HirExpr::Literal(Literal::Int(1)),
1678 type_annotation: None,
1679 }];
1680 preload_hir_type_annotations(&body, &mut ctx);
1681 assert!(ctx.var_types.get("x").is_none());
1682 }
1683
1684 #[test]
1685 fn test_preload_in_if_branches() {
1686 let mut ctx = CodeGenContext::default();
1687 let body = vec![HirStmt::If {
1688 condition: HirExpr::Literal(Literal::Bool(true)),
1689 then_body: vec![HirStmt::Assign {
1690 target: AssignTarget::Symbol("a".to_string()),
1691 value: HirExpr::Literal(Literal::Int(1)),
1692 type_annotation: Some(Type::Int),
1693 }],
1694 else_body: Some(vec![HirStmt::Assign {
1695 target: AssignTarget::Symbol("b".to_string()),
1696 value: HirExpr::Literal(Literal::String("hi".to_string())),
1697 type_annotation: Some(Type::String),
1698 }]),
1699 }];
1700 preload_hir_type_annotations(&body, &mut ctx);
1701 assert_eq!(ctx.var_types.get("a"), Some(&Type::Int));
1702 assert_eq!(ctx.var_types.get("b"), Some(&Type::String));
1703 }
1704
1705 #[test]
1706 fn test_preload_in_while_loop() {
1707 let mut ctx = CodeGenContext::default();
1708 let body = vec![HirStmt::While {
1709 condition: HirExpr::Literal(Literal::Bool(true)),
1710 body: vec![HirStmt::Assign {
1711 target: AssignTarget::Symbol("counter".to_string()),
1712 value: HirExpr::Literal(Literal::Int(0)),
1713 type_annotation: Some(Type::Int),
1714 }],
1715 }];
1716 preload_hir_type_annotations(&body, &mut ctx);
1717 assert_eq!(ctx.var_types.get("counter"), Some(&Type::Int));
1718 }
1719
1720 #[test]
1721 fn test_preload_in_for_loop() {
1722 let mut ctx = CodeGenContext::default();
1723 let body = vec![HirStmt::For {
1724 target: AssignTarget::Symbol("i".to_string()),
1725 iter: HirExpr::Call {
1726 func: "range".to_string(),
1727 args: vec![HirExpr::Literal(Literal::Int(10))],
1728 kwargs: vec![],
1729 },
1730 body: vec![HirStmt::Assign {
1731 target: AssignTarget::Symbol("total".to_string()),
1732 value: HirExpr::Literal(Literal::Int(0)),
1733 type_annotation: Some(Type::Float),
1734 }],
1735 }];
1736 preload_hir_type_annotations(&body, &mut ctx);
1737 assert_eq!(ctx.var_types.get("total"), Some(&Type::Float));
1738 }
1739
1740 #[test]
1741 fn test_preload_in_try_block() {
1742 let mut ctx = CodeGenContext::default();
1743 let body = vec![HirStmt::Try {
1744 body: vec![HirStmt::Assign {
1745 target: AssignTarget::Symbol("result".to_string()),
1746 value: HirExpr::Literal(Literal::Int(0)),
1747 type_annotation: Some(Type::Int),
1748 }],
1749 handlers: vec![ExceptHandler {
1750 exception_type: Some("ValueError".to_string()),
1751 name: None,
1752 body: vec![HirStmt::Assign {
1753 target: AssignTarget::Symbol("err_msg".to_string()),
1754 value: HirExpr::Literal(Literal::String("error".to_string())),
1755 type_annotation: Some(Type::String),
1756 }],
1757 }],
1758 orelse: Some(vec![HirStmt::Assign {
1759 target: AssignTarget::Symbol("ok".to_string()),
1760 value: HirExpr::Literal(Literal::Bool(true)),
1761 type_annotation: Some(Type::Bool),
1762 }]),
1763 finalbody: Some(vec![HirStmt::Assign {
1764 target: AssignTarget::Symbol("done".to_string()),
1765 value: HirExpr::Literal(Literal::Bool(true)),
1766 type_annotation: Some(Type::Bool),
1767 }]),
1768 }];
1769 preload_hir_type_annotations(&body, &mut ctx);
1770 assert_eq!(ctx.var_types.get("result"), Some(&Type::Int));
1771 assert_eq!(ctx.var_types.get("err_msg"), Some(&Type::String));
1772 assert_eq!(ctx.var_types.get("ok"), Some(&Type::Bool));
1773 assert_eq!(ctx.var_types.get("done"), Some(&Type::Bool));
1774 }
1775
1776 #[test]
1777 fn test_preload_in_with_block() {
1778 let mut ctx = CodeGenContext::default();
1779 let body = vec![HirStmt::With {
1780 context: HirExpr::Call {
1781 func: "open".to_string(),
1782 args: vec![],
1783 kwargs: vec![],
1784 },
1785 target: Some("f".to_string()),
1786 body: vec![HirStmt::Assign {
1787 target: AssignTarget::Symbol("data".to_string()),
1788 value: HirExpr::Literal(Literal::String("".to_string())),
1789 type_annotation: Some(Type::String),
1790 }],
1791 is_async: false,
1792 }];
1793 preload_hir_type_annotations(&body, &mut ctx);
1794 assert_eq!(ctx.var_types.get("data"), Some(&Type::String));
1795 }
1796
1797 #[test]
1798 fn test_preload_in_nested_function() {
1799 let mut ctx = CodeGenContext::default();
1800 let body = vec![HirStmt::FunctionDef {
1801 name: "inner".to_string(),
1802 params: Box::new(smallvec::smallvec![]),
1803 ret_type: Type::None,
1804 body: vec![HirStmt::Assign {
1805 target: AssignTarget::Symbol("local".to_string()),
1806 value: HirExpr::Literal(Literal::Int(0)),
1807 type_annotation: Some(Type::Int),
1808 }],
1809 docstring: None,
1810 }];
1811 preload_hir_type_annotations(&body, &mut ctx);
1812 assert_eq!(ctx.var_types.get("local"), Some(&Type::Int));
1813 }
1814
1815 #[test]
1816 fn test_preload_in_block() {
1817 let mut ctx = CodeGenContext::default();
1818 let body = vec![HirStmt::Block(vec![HirStmt::Assign {
1819 target: AssignTarget::Symbol("x".to_string()),
1820 value: HirExpr::Literal(Literal::Int(1)),
1821 type_annotation: Some(Type::Int),
1822 }])];
1823 preload_hir_type_annotations(&body, &mut ctx);
1824 assert_eq!(ctx.var_types.get("x"), Some(&Type::Int));
1825 }
1826
1827 #[test]
1828 fn test_preload_empty_body() {
1829 let mut ctx = CodeGenContext::default();
1830 preload_hir_type_annotations(&[], &mut ctx);
1831 assert!(ctx.var_types.is_empty());
1832 }
1833
1834 #[test]
1835 fn test_preload_tuple_target_ignored() {
1836 let mut ctx = CodeGenContext::default();
1837 let body = vec![HirStmt::Assign {
1838 target: AssignTarget::Tuple(vec![
1839 AssignTarget::Symbol("a".to_string()),
1840 AssignTarget::Symbol("b".to_string()),
1841 ]),
1842 value: HirExpr::Tuple(vec![
1843 HirExpr::Literal(Literal::Int(1)),
1844 HirExpr::Literal(Literal::Int(2)),
1845 ]),
1846 type_annotation: Some(Type::Int),
1847 }];
1848 preload_hir_type_annotations(&body, &mut ctx);
1849 assert!(ctx.var_types.get("a").is_none());
1851 }
1852
1853 fn transpile(python_code: &str) -> String {
1856 use crate::ast_bridge::AstBridge;
1857 use crate::rust_gen::generate_rust_file;
1858 use crate::type_mapper::TypeMapper;
1859 use rustpython_parser::{parse, Mode};
1860
1861 let ast = parse(python_code, Mode::Module, "<test>").expect("parse");
1862 let (module, _) = AstBridge::new()
1863 .with_source(python_code.to_string())
1864 .python_to_hir(ast)
1865 .expect("hir");
1866 let tm = TypeMapper::default();
1867 let (result, _) = generate_rust_file(&module, &tm).expect("codegen");
1868 result
1869 }
1870
1871 #[test]
1872 fn test_transpile_nested_function_return() {
1873 let code = r#"
1874def make_adder(n: int) -> int:
1875 def adder(x: int) -> int:
1876 return n + x
1877 return adder
1878"#;
1879 let rust = transpile(code);
1880 assert!(rust.contains("fn make_adder"));
1881 }
1882
1883 #[test]
1884 fn test_transpile_function_with_type_annotations() {
1885 let code = r#"
1886def greet(name: str) -> str:
1887 result: str = "Hello, " + name
1888 return result
1889"#;
1890 let rust = transpile(code);
1891 assert!(rust.contains("fn greet"));
1892 assert!(rust.contains("String") || rust.contains("str"));
1893 }
1894
1895 #[test]
1896 fn test_transpile_function_return_type_inferred() {
1897 let code = r#"
1898def double(x: int) -> int:
1899 return x * 2
1900"#;
1901 let rust = transpile(code);
1902 assert!(rust.contains("fn double"));
1903 assert!(rust.contains("i64") || rust.contains("i32"));
1904 }
1905
1906 #[test]
1907 fn test_transpile_async_function() {
1908 let code = r#"
1909async def fetch_data(url: str) -> str:
1910 return url
1911"#;
1912 let rust = transpile(code);
1913 assert!(rust.contains("fn fetch_data"));
1916 assert!(rust.contains("String") || rust.contains("str"));
1917 }
1918
1919 fn make_function(
1922 name: &str,
1923 params: Vec<HirParam>,
1924 ret_type: Type,
1925 body: Vec<HirStmt>,
1926 ) -> HirFunction {
1927 HirFunction {
1928 name: name.to_string(),
1929 params: params.into_iter().collect(),
1930 ret_type,
1931 body,
1932 properties: Default::default(),
1933 annotations: Default::default(),
1934 docstring: None,
1935 }
1936 }
1937
1938 #[test]
1939 fn test_detect_nested_no_nested_functions() {
1940 let mut ctx = CodeGenContext::default();
1941 let func = make_function(
1942 "simple",
1943 vec![],
1944 Type::Int,
1945 vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
1946 );
1947 let result = detect_returns_nested_function(&func, &mut ctx);
1948 assert!(result.is_none());
1949 }
1950
1951 #[test]
1952 fn test_detect_nested_with_explicit_return() {
1953 let mut ctx = CodeGenContext::default();
1954 let func = make_function(
1955 "make_adder",
1956 vec![HirParam {
1957 name: "n".to_string(),
1958 ty: Type::Int,
1959 default: None,
1960 is_vararg: false,
1961 }],
1962 Type::Unknown,
1963 vec![
1964 HirStmt::FunctionDef {
1965 name: "adder".to_string(),
1966 params: Box::new(smallvec::smallvec![HirParam {
1967 name: "x".to_string(),
1968 ty: Type::Int,
1969 default: None,
1970 is_vararg: false,
1971 }]),
1972 ret_type: Type::Int,
1973 body: vec![HirStmt::Return(Some(HirExpr::Binary {
1974 left: Box::new(HirExpr::Var("n".to_string())),
1975 op: BinOp::Add,
1976 right: Box::new(HirExpr::Var("x".to_string())),
1977 }))],
1978 docstring: None,
1979 },
1980 HirStmt::Return(Some(HirExpr::Var("adder".to_string()))),
1981 ],
1982 );
1983 let result = detect_returns_nested_function(&func, &mut ctx);
1984 assert!(result.is_some());
1985 let (name, params, ret_type) = result.unwrap();
1986 assert_eq!(name, "adder");
1987 assert_eq!(params.len(), 1);
1988 assert_eq!(params[0].name, "x");
1989 assert!(matches!(ret_type, Type::Int));
1990 }
1991
1992 #[test]
1993 fn test_detect_nested_with_implicit_return() {
1994 let mut ctx = CodeGenContext::default();
1995 let func = make_function(
1996 "make_fn",
1997 vec![],
1998 Type::Unknown,
1999 vec![
2000 HirStmt::FunctionDef {
2001 name: "inner".to_string(),
2002 params: Box::new(smallvec::smallvec![]),
2003 ret_type: Type::String,
2004 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::String(
2005 "hello".to_string(),
2006 ))))],
2007 docstring: None,
2008 },
2009 HirStmt::Expr(HirExpr::Var("inner".to_string())),
2010 ],
2011 );
2012 let result = detect_returns_nested_function(&func, &mut ctx);
2013 assert!(result.is_some());
2014 let (name, _, ret_type) = result.unwrap();
2015 assert_eq!(name, "inner");
2016 assert!(matches!(ret_type, Type::String));
2017 }
2018
2019 #[test]
2020 fn test_detect_nested_returns_wrong_name() {
2021 let mut ctx = CodeGenContext::default();
2022 let func = make_function(
2023 "make_fn",
2024 vec![],
2025 Type::Unknown,
2026 vec![
2027 HirStmt::FunctionDef {
2028 name: "inner".to_string(),
2029 params: Box::new(smallvec::smallvec![]),
2030 ret_type: Type::Int,
2031 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
2032 docstring: None,
2033 },
2034 HirStmt::Return(Some(HirExpr::Var("other_fn".to_string()))),
2035 ],
2036 );
2037 let result = detect_returns_nested_function(&func, &mut ctx);
2038 assert!(result.is_none());
2039 }
2040
2041 #[test]
2042 fn test_detect_nested_stores_params_in_ctx() {
2043 let mut ctx = CodeGenContext::default();
2044 let func = make_function(
2045 "factory",
2046 vec![],
2047 Type::Unknown,
2048 vec![
2049 HirStmt::FunctionDef {
2050 name: "product".to_string(),
2051 params: Box::new(smallvec::smallvec![
2052 HirParam {
2053 name: "a".to_string(),
2054 ty: Type::Int,
2055 default: None,
2056 is_vararg: false,
2057 },
2058 HirParam {
2059 name: "b".to_string(),
2060 ty: Type::Float,
2061 default: None,
2062 is_vararg: false,
2063 },
2064 ]),
2065 ret_type: Type::Float,
2066 body: vec![],
2067 docstring: None,
2068 },
2069 HirStmt::Return(Some(HirExpr::Var("product".to_string()))),
2070 ],
2071 );
2072 let _ = detect_returns_nested_function(&func, &mut ctx);
2073 assert!(ctx.nested_function_params.contains_key("product"));
2074 let params = ctx.nested_function_params.get("product").unwrap();
2075 assert_eq!(params.len(), 2);
2076 }
2077
2078 #[test]
2079 fn test_detect_nested_unknown_param_inference() {
2080 let mut ctx = CodeGenContext::default();
2081 let func = make_function(
2082 "make_fn",
2083 vec![],
2084 Type::Unknown,
2085 vec![
2086 HirStmt::FunctionDef {
2087 name: "inner".to_string(),
2088 params: Box::new(smallvec::smallvec![HirParam {
2089 name: "x".to_string(),
2090 ty: Type::Unknown,
2091 default: None,
2092 is_vararg: false,
2093 }]),
2094 ret_type: Type::Unknown,
2095 body: vec![HirStmt::Return(Some(HirExpr::Binary {
2096 left: Box::new(HirExpr::Var("x".to_string())),
2097 op: BinOp::Add,
2098 right: Box::new(HirExpr::Literal(Literal::Int(1))),
2099 }))],
2100 docstring: None,
2101 },
2102 HirStmt::Return(Some(HirExpr::Var("inner".to_string()))),
2103 ],
2104 );
2105 let result = detect_returns_nested_function(&func, &mut ctx);
2106 assert!(result.is_some());
2107 }
2108
2109 #[test]
2110 fn test_detect_nested_optional_unknown_param() {
2111 let mut ctx = CodeGenContext::default();
2112 let func = make_function(
2113 "make_fn",
2114 vec![],
2115 Type::Unknown,
2116 vec![
2117 HirStmt::FunctionDef {
2118 name: "inner".to_string(),
2119 params: Box::new(smallvec::smallvec![HirParam {
2120 name: "val".to_string(),
2121 ty: Type::Optional(Box::new(Type::Unknown)),
2122 default: None,
2123 is_vararg: false,
2124 }]),
2125 ret_type: Type::Int,
2126 body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(0))))],
2127 docstring: None,
2128 },
2129 HirStmt::Return(Some(HirExpr::Var("inner".to_string()))),
2130 ],
2131 );
2132 let result = detect_returns_nested_function(&func, &mut ctx);
2133 assert!(result.is_some());
2134 }
2135
2136 #[test]
2137 fn test_detect_nested_empty_body() {
2138 let mut ctx = CodeGenContext::default();
2139 let func = make_function("empty", vec![], Type::Unknown, vec![]);
2140 let result = detect_returns_nested_function(&func, &mut ctx);
2141 assert!(result.is_none());
2142 }
2143
2144 #[test]
2145 fn test_detect_nested_non_var_return() {
2146 let mut ctx = CodeGenContext::default();
2147 let func = make_function(
2148 "make_fn",
2149 vec![],
2150 Type::Unknown,
2151 vec![
2152 HirStmt::FunctionDef {
2153 name: "inner".to_string(),
2154 params: Box::new(smallvec::smallvec![]),
2155 ret_type: Type::Int,
2156 body: vec![],
2157 docstring: None,
2158 },
2159 HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42)))),
2160 ],
2161 );
2162 let result = detect_returns_nested_function(&func, &mut ctx);
2163 assert!(result.is_none()); }
2165
2166 #[test]
2169 fn test_transpile_closure_returning_function() {
2170 let code = r#"
2171def make_multiplier(factor: int):
2172 def multiply(x: int) -> int:
2173 return x * factor
2174 return multiply
2175"#;
2176 let rust = transpile(code);
2177 assert!(rust.contains("fn make_multiplier"));
2178 }
2179
2180 #[test]
2181 fn test_transpile_function_with_default_params() {
2182 let code = r#"
2183def greet(name: str, greeting: str = "Hello") -> str:
2184 return greeting + " " + name
2185"#;
2186 let rust = transpile(code);
2187 assert!(rust.contains("fn greet"));
2188 }
2189
2190 #[test]
2191 fn test_transpile_function_returning_bool() {
2192 let code = r#"
2193def is_even(n: int) -> bool:
2194 return n % 2 == 0
2195"#;
2196 let rust = transpile(code);
2197 assert!(rust.contains("fn is_even"));
2198 assert!(rust.contains("bool"));
2199 }
2200
2201 #[test]
2202 fn test_transpile_function_returning_list() {
2203 let code = r#"
2204def make_list(n: int) -> list:
2205 result = []
2206 for i in range(n):
2207 result.append(i)
2208 return result
2209"#;
2210 let rust = transpile(code);
2211 assert!(rust.contains("fn make_list"));
2212 assert!(rust.contains("Vec"));
2213 }
2214
2215 #[test]
2216 fn test_transpile_function_returning_optional() {
2217 let code = r#"
2218def find_item(items: list, target: int):
2219 for item in items:
2220 if item == target:
2221 return item
2222 return None
2223"#;
2224 let rust = transpile(code);
2225 assert!(rust.contains("fn find_item"));
2226 assert!(rust.contains("Option") || rust.contains("None"));
2227 }
2228
2229 #[test]
2230 fn test_transpile_function_with_multiple_returns() {
2231 let code = r#"
2232def classify(x: int) -> str:
2233 if x > 0:
2234 return "positive"
2235 elif x < 0:
2236 return "negative"
2237 else:
2238 return "zero"
2239"#;
2240 let rust = transpile(code);
2241 assert!(rust.contains("fn classify"));
2242 assert!(rust.contains("positive"));
2243 assert!(rust.contains("negative"));
2244 assert!(rust.contains("zero"));
2245 }
2246
2247 #[test]
2250 fn test_s9b6_infer_dict_return() {
2251 let code = r#"
2252def make_dict() -> dict:
2253 d = {}
2254 d["a"] = 1
2255 d["b"] = 2
2256 return d
2257"#;
2258 let rust = transpile(code);
2259 assert!(rust.contains("fn make_dict"), "output: {}", rust);
2260 }
2261
2262 #[test]
2263 fn test_s9b6_infer_tuple_return() {
2264 let code = r#"
2265def pair(a: int, b: int) -> tuple:
2266 return a, b
2267"#;
2268 let rust = transpile(code);
2269 assert!(rust.contains("fn pair"), "output: {}", rust);
2270 }
2271
2272 #[test]
2273 fn test_s9b6_infer_set_return() {
2274 let code = r#"
2275def unique_chars(s: str) -> set:
2276 result = set()
2277 for c in s:
2278 result.add(c)
2279 return result
2280"#;
2281 let rust = transpile(code);
2282 assert!(rust.contains("fn unique_chars"), "output: {}", rust);
2283 }
2284
2285 #[test]
2286 fn test_s9b6_infer_from_binary_op() {
2287 let code = r#"
2288def compute(a: int, b: int):
2289 return a * b + a - b
2290"#;
2291 let rust = transpile(code);
2292 assert!(rust.contains("fn compute"), "output: {}", rust);
2293 }
2294
2295 #[test]
2296 fn test_s9b6_infer_from_comparison() {
2297 let code = r#"
2298def check(x: int, y: int):
2299 return x > y
2300"#;
2301 let rust = transpile(code);
2302 assert!(rust.contains("fn check"), "output: {}", rust);
2303 assert!(rust.contains("bool"), "should infer bool: {}", rust);
2304 }
2305
2306 #[test]
2307 fn test_s9b6_infer_from_method_call() {
2308 let code = r#"
2309def upper(s: str):
2310 return s.upper()
2311"#;
2312 let rust = transpile(code);
2313 assert!(rust.contains("fn upper"), "output: {}", rust);
2314 }
2315
2316 #[test]
2317 fn test_s9b6_infer_from_len() {
2318 let code = r#"
2319def count(items: list):
2320 return len(items)
2321"#;
2322 let rust = transpile(code);
2323 assert!(rust.contains("fn count"), "output: {}", rust);
2324 }
2325
2326 #[test]
2327 fn test_s9b6_infer_from_sum() {
2328 let code = r#"
2329def total(nums: list):
2330 return sum(nums)
2331"#;
2332 let rust = transpile(code);
2333 assert!(rust.contains("fn total"), "output: {}", rust);
2334 }
2335
2336 #[test]
2337 fn test_s9b6_infer_from_min_max() {
2338 let code = r#"
2339def largest(nums: list):
2340 return max(nums)
2341"#;
2342 let rust = transpile(code);
2343 assert!(rust.contains("fn largest"), "output: {}", rust);
2344 }
2345
2346 #[test]
2347 fn test_s9b6_infer_from_str_join() {
2348 let code = r#"
2349def combine(parts: list):
2350 return ",".join(parts)
2351"#;
2352 let rust = transpile(code);
2353 assert!(rust.contains("fn combine"), "output: {}", rust);
2354 }
2355
2356 #[test]
2357 fn test_s9b6_infer_from_str_split() {
2358 let code = r#"
2359def split_csv(line: str):
2360 return line.split(",")
2361"#;
2362 let rust = transpile(code);
2363 assert!(rust.contains("fn split_csv"), "output: {}", rust);
2364 }
2365
2366 #[test]
2367 fn test_s9b6_infer_void_function() {
2368 let code = r#"
2369def log(msg: str):
2370 print(msg)
2371"#;
2372 let rust = transpile(code);
2373 assert!(rust.contains("fn log"), "output: {}", rust);
2374 }
2375
2376 #[test]
2377 fn test_s9b6_infer_from_list_comprehension() {
2378 let code = r#"
2379def doubles(nums: list):
2380 return [x * 2 for x in nums]
2381"#;
2382 let rust = transpile(code);
2383 assert!(rust.contains("fn doubles"), "output: {}", rust);
2384 }
2385
2386 #[test]
2387 fn test_s9b6_infer_nested_return() {
2388 let code = r#"
2389def safe_div(a: int, b: int):
2390 if b == 0:
2391 return None
2392 return a // b
2393"#;
2394 let rust = transpile(code);
2395 assert!(rust.contains("fn safe_div"), "output: {}", rust);
2396 }
2397
2398 #[test]
2399 fn test_s9b6_infer_from_isinstance() {
2400 let code = r#"
2401def is_str(x) -> bool:
2402 return isinstance(x, str)
2403"#;
2404 let rust = transpile(code);
2405 assert!(rust.contains("fn is_str"), "output: {}", rust);
2406 }
2407
2408 #[test]
2409 fn test_s9b6_infer_multiple_exit_paths() {
2410 let code = r#"
2411def analyze(x: int):
2412 if x > 0:
2413 return "positive"
2414 return "non-positive"
2415"#;
2416 let rust = transpile(code);
2417 assert!(rust.contains("fn analyze"), "output: {}", rust);
2418 }
2419}