1#[cfg(feature = "decision-tracing")]
7use crate::decision_trace::DecisionCategory;
8use crate::hir::*;
9use crate::rust_gen::context::{CodeGenContext, RustCodeGen, ToRustExpr};
10use crate::rust_gen::control_stmt_helpers::{
11 codegen_break_stmt, codegen_continue_stmt, codegen_pass_stmt,
12};
13use crate::rust_gen::expr_analysis::{
14 contains_floor_div, extract_divisor_from_floor_div, handler_contains_raise,
15 is_nested_function_recursive, to_pascal_case,
16};
17use crate::rust_gen::func_gen::propagate_return_type_to_vars;
18use crate::rust_gen::keywords::safe_ident;
19use crate::rust_gen::rust_type_to_syn;
20use crate::rust_gen::stmt_gen::{
21 codegen_assert_stmt, codegen_assign_stmt, codegen_expr_stmt, codegen_for_stmt, codegen_if_stmt,
22 codegen_raise_stmt, codegen_return_stmt, codegen_while_stmt, codegen_with_stmt,
23 find_variable_type, infer_try_body_return_type, try_generate_json_stdin_match,
24 try_return_type_to_tokens,
25};
26use crate::rust_gen::type_tokens::hir_type_to_tokens;
27use crate::rust_gen::var_analysis::extract_toplevel_assigned_symbols;
28use crate::trace_decision;
29use anyhow::Result;
30use quote::quote;
31
32#[inline]
34pub(crate) fn codegen_try_stmt(
35 body: &[HirStmt],
36 handlers: &[ExceptHandler],
37 finalbody: &Option<Vec<HirStmt>>,
38 ctx: &mut CodeGenContext,
39) -> Result<proc_macro2::TokenStream> {
40 trace_decision!(
42 category = DecisionCategory::ErrorHandling,
43 name = "try_except",
44 chosen = "match_result",
45 alternatives = [
46 "unwrap_or",
47 "question_mark",
48 "anyhow_context",
49 "custom_error"
50 ],
51 confidence = 0.80
52 );
53
54 let try_vars = extract_toplevel_assigned_symbols(body);
59 let handler_vars: std::collections::HashSet<String> = handlers
60 .iter()
61 .flat_map(|h| extract_toplevel_assigned_symbols(&h.body))
62 .collect();
63
64 let hoisted_try_vars: Vec<String> = try_vars
67 .union(&handler_vars)
68 .filter(|v| !ctx.is_declared(v))
69 .cloned()
70 .collect();
71
72 let mut hoisted_decls = Vec::new();
74 for var_name in &hoisted_try_vars {
75 let var_ident = safe_ident(var_name);
76
77 let var_type = find_variable_type(var_name, body).or_else(|| {
79 handlers
80 .iter()
81 .find_map(|h| find_variable_type(var_name, &h.body))
82 });
83
84 if let Some(ty) = var_type {
85 let needs_option_wrap = matches!(
88 &ty,
89 Type::Custom(s) if s == "std::process::Child" || s == "Child"
90 );
91
92 if needs_option_wrap {
93 let opt_type = Type::Optional(Box::new(ty.clone()));
95 let rust_type = ctx.type_mapper.map_type(&opt_type);
96 let syn_type = rust_type_to_syn(&rust_type)?;
97 hoisted_decls.push(quote! { let mut #var_ident: #syn_type = None; });
98 ctx.var_types.insert(var_name.clone(), opt_type);
99 } else {
100 let rust_type = ctx.type_mapper.map_type(&ty);
101 let syn_type = rust_type_to_syn(&rust_type)?;
102 hoisted_decls.push(quote! { let mut #var_ident: #syn_type = Default::default(); });
107 ctx.var_types.insert(var_name.clone(), ty);
108 }
109 } else {
110 let value_type = crate::hir::Type::Custom("serde_json::Value".to_string());
114 let opt_type = crate::hir::Type::Optional(Box::new(value_type));
115
116 ctx.var_types.insert(var_name.clone(), opt_type);
117 hoisted_decls.push(quote! { let mut #var_ident: Option<serde_json::Value> = None; });
118
119 ctx.hoisted_inference_vars.insert(var_name.clone());
121 }
122
123 ctx.declare_var(var_name);
125 }
126
127 if let Some(result) = try_generate_json_stdin_match(body, handlers, finalbody, ctx)? {
132 return Ok(result);
133 }
134
135 let simple_pattern_info = if body.len() == 1
141 && handlers.len() == 1
142 && handlers[0].body.len() == 1
143 && handlers[0].name.is_none()
144 {
146 match &handlers[0].body[0] {
148 HirStmt::Return(Some(HirExpr::Literal(lit))) => Some((
150 (match lit {
151 Literal::Int(n) => n.to_string(),
152 Literal::Float(f) => f.to_string(),
153 Literal::String(s) => format!("\"{}\"", s),
154 Literal::Bool(b) => b.to_string(),
155 _ => "Default::default()".to_string(),
156 })
157 .to_string(),
158 handlers[0].exception_type.clone(),
159 )),
160 HirStmt::Return(Some(HirExpr::Unary { op, operand })) => {
162 if let HirExpr::Literal(lit) = &**operand {
163 match (op, lit) {
164 (crate::hir::UnaryOp::Neg, Literal::Int(n)) => {
165 Some((format!("-{}", n), handlers[0].exception_type.clone()))
166 }
167 (crate::hir::UnaryOp::Neg, Literal::Float(f)) => {
168 Some((format!("-{}", f), handlers[0].exception_type.clone()))
169 }
170 _ => None,
171 }
172 } else {
173 None
174 }
175 }
176 _ => None,
177 }
178 } else {
179 None
180 };
181
182 let handled_types: Vec<String> = handlers
184 .iter()
185 .filter_map(|h| h.exception_type.clone())
186 .collect();
187
188 ctx.enter_try_scope(handled_types.clone());
191
192 let has_zero_div_handler = handlers
194 .iter()
195 .any(|h| h.exception_type.as_deref() == Some("ZeroDivisionError"));
196
197 if has_zero_div_handler && body.len() == 1 {
198 if let HirStmt::Return(Some(expr)) = &body[0] {
199 if contains_floor_div(expr) {
200 let divisor_expr = extract_divisor_from_floor_div(expr)?;
202 let divisor_tokens = divisor_expr.to_rust_expr(ctx)?;
203
204 let zero_div_handler_idx = handlers
206 .iter()
207 .position(|h| h.exception_type.as_deref() == Some("ZeroDivisionError"))
208 .unwrap();
209
210 ctx.enter_scope();
212 let old_is_final = ctx.is_final_statement;
214 ctx.is_final_statement = false;
215 let handler_stmts: Vec<_> = handlers[zero_div_handler_idx]
216 .body
217 .iter()
218 .map(|s| s.to_rust_tokens(ctx))
219 .collect::<Result<Vec<_>>>()?;
220 ctx.is_final_statement = old_is_final;
221 ctx.exit_scope();
222
223 let floor_div_result = expr.to_rust_expr(ctx)?;
225
226 ctx.exit_exception_scope();
228
229 if let Some(finalbody) = finalbody {
231 ctx.enter_scope();
232 let finally_stmts: Vec<_> = finalbody
233 .iter()
234 .map(|s| s.to_rust_tokens(ctx))
235 .collect::<Result<Vec<_>>>()?;
236 ctx.exit_scope();
237
238 return if ctx.current_function_can_fail {
240 Ok(quote! {
241 {
242 if #divisor_tokens == 0 {
243 #(#handler_stmts)*
244 } else {
245 return Ok(#floor_div_result);
246 }
247 #(#finally_stmts)*
248 }
249 })
250 } else {
251 Ok(quote! {
252 {
253 if #divisor_tokens == 0 {
254 #(#handler_stmts)*
255 } else {
256 return #floor_div_result;
257 }
258 #(#finally_stmts)*
259 }
260 })
261 };
262 } else {
263 return if ctx.current_function_can_fail {
265 Ok(quote! {
266 if #divisor_tokens == 0 {
267 #(#handler_stmts)*
268 } else {
269 return Ok(#floor_div_result);
270 }
271 })
272 } else {
273 Ok(quote! {
274 if #divisor_tokens == 0 {
275 #(#handler_stmts)*
276 } else {
277 return #floor_div_result;
278 }
279 })
280 };
281 }
282 }
283 }
284 }
285
286 let saved_is_final = ctx.is_final_statement;
291 ctx.is_final_statement = false;
292
293 ctx.enter_scope();
294 let try_stmts: Vec<_> = body
295 .iter()
296 .map(|s| s.to_rust_tokens(ctx))
297 .collect::<Result<Vec<_>>>()?;
298 ctx.exit_scope();
299
300 ctx.is_final_statement = saved_is_final;
302
303 ctx.exit_exception_scope();
305
306 let mut handler_tokens = Vec::new();
308 for handler in handlers {
309 ctx.enter_handler_scope();
311 ctx.enter_scope();
312
313 if let Some(var_name) = &handler.name {
315 ctx.declare_var(var_name);
316 }
317
318 let saved_is_final = ctx.is_final_statement;
322 ctx.is_final_statement = false;
323
324 let handler_stmts: Vec<_> = handler
325 .body
326 .iter()
327 .map(|s| s.to_rust_tokens(ctx))
328 .collect::<Result<Vec<_>>>()?;
329
330 ctx.is_final_statement = saved_is_final;
332 ctx.exit_scope();
333 ctx.exit_exception_scope();
335
336 let is_nested = ctx.exception_nesting_depth() > 0;
339 let handler_stmts_transformed: Vec<_> = if is_nested {
340 handler_stmts
341 .iter()
342 .map(|stmt| {
343 let stmt_str = stmt.to_string();
344 if stmt_str.starts_with("return ") && !stmt_str.starts_with("return Ok (") {
345 if let Some(expr_part) = stmt_str.strip_prefix("return ") {
346 if let Some(expr) = expr_part.strip_suffix(" ;") {
347 let wrapped = format!("return Ok({}) ;", expr);
348 return wrapped.parse().unwrap_or_else(|_| stmt.clone());
349 }
350 }
351 }
352 stmt.clone()
353 })
354 .collect()
355 } else {
356 handler_stmts
357 };
358
359 handler_tokens.push(quote! { #(#handler_stmts_transformed)* });
360 }
361
362 let finally_stmts = if let Some(finally_body) = finalbody {
364 let stmts: Vec<_> = finally_body
365 .iter()
366 .map(|s| s.to_rust_tokens(ctx))
367 .collect::<Result<Vec<_>>>()?;
368 Some(quote! { #(#stmts)* })
369 } else {
370 None
371 };
372
373 if handlers.is_empty() {
375 if let Some(finally_code) = finally_stmts {
377 Ok(quote! {
378 #(#hoisted_decls)*
379 {
380 #(#try_stmts)*
381 #finally_code
382 }
383 })
384 } else {
385 Ok(quote! {
387 #(#hoisted_decls)*
388 #(#try_stmts)*
389 })
390 }
391 } else {
392 if handlers.len() == 1 {
395 if let Some((var_name, parse_expr_str, remaining_stmts)) =
396 extract_parse_from_tokens(&try_stmts)
397 {
398 let parse_expr: proc_macro2::TokenStream = match parse_expr_str.parse() {
400 Ok(ts) => ts,
401 Err(_) => return Ok(quote! { #(#try_stmts)* }), };
403 let ok_var = safe_ident(&var_name);
404
405 let ok_body = quote! { #(#remaining_stmts)* };
407
408 let err_body = &handler_tokens[0];
410
411 let err_pattern = if let Some(exc_var) = &handlers[0].name {
413 let exc_ident = safe_ident(exc_var);
415 quote! { Err(#exc_ident) }
416 } else {
417 quote! { Err(_) }
419 };
420
421 let match_expr = quote! {
423 match #parse_expr {
424 Ok(#ok_var) => { #ok_body },
425 #err_pattern => { #err_body }
426 }
427 };
428
429 if let Some(finally_code) = finally_stmts {
431 return Ok(quote! {
432 {
433 #match_expr
434 #finally_code
435 }
436 });
437 } else {
438 return Ok(match_expr);
439 }
440 }
441 }
442
443 if let Some((exception_value_str, _exception_type)) = simple_pattern_info {
445 let try_code = quote! { #(#try_stmts)* };
448 let try_str = try_code.to_string();
449
450 if try_str.contains("unwrap_or_default") {
454 let fixed_code = try_str
457 .replace(
458 "unwrap_or_default ()",
459 &format!("unwrap_or ({})", exception_value_str),
460 )
461 .replace(
462 "unwrap_or_default()",
463 &format!("unwrap_or({})", exception_value_str),
464 );
465
466 let fixed_tokens: proc_macro2::TokenStream = fixed_code.parse().unwrap_or(try_code);
468
469 if let Some(finally_code) = finally_stmts {
471 Ok(quote! {
472 {
473 #(#hoisted_decls)*
474 #fixed_tokens
475 #finally_code
476 }
477 })
478 } else if hoisted_decls.is_empty() {
479 Ok(fixed_tokens)
480 } else {
481 Ok(quote! {
482 #(#hoisted_decls)*
483 #fixed_tokens
484 })
485 }
486 } else {
487 let handler_code = &handler_tokens[0];
492 if let Some(finally_code) = finally_stmts {
493 Ok(quote! {
494 {
495 #(#hoisted_decls)*
496 #(#try_stmts)*
497 #handler_code
498 #finally_code
499 }
500 })
501 } else {
502 Ok(quote! {
503 {
504 #(#hoisted_decls)*
505 #(#try_stmts)*
506 #handler_code
507 }
508 })
509 }
510 }
511 } else {
512 let try_return_type = infer_try_body_return_type(body, ctx);
520 let return_type_tokens = try_return_type
521 .as_ref()
522 .map(try_return_type_to_tokens)
523 .unwrap_or_else(|| quote! { () });
524 let ok_value = try_return_type
525 .as_ref()
526 .map(|_| quote! { _result })
527 .unwrap_or_else(|| quote! { () });
528
529 let any_handler_raises = handlers.iter().any(|h| handler_contains_raise(&h.body));
535 let ok_arm_body = if try_return_type.is_some() {
536 if any_handler_raises
539 || ctx.exception_nesting_depth() > 0
540 || ctx.current_function_can_fail
541 {
542 quote! { return Ok(_result); }
543 } else {
544 quote! { return _result; }
545 }
546 } else {
547 quote! {}
548 };
549
550 let try_stmts_transformed: Vec<_> = try_stmts
553 .iter()
554 .map(|stmt| {
555 let stmt_str = stmt.to_string();
556 if stmt_str.starts_with("return ") && !stmt_str.starts_with("return Ok (") {
559 if let Some(expr_part) = stmt_str.strip_prefix("return ") {
560 if let Some(expr) = expr_part.strip_suffix(" ;") {
561 let wrapped = format!("return Ok({}) ;", expr);
562 return wrapped.parse().unwrap_or_else(|_| stmt.clone());
563 }
564 }
565 }
566 stmt.clone()
567 })
568 .collect();
569
570 let always_returns = body.iter().any(|s| matches!(s, HirStmt::Return(_)));
572
573 let closure_fallback = if always_returns {
576 quote! {}
577 } else if try_return_type.is_none() {
578 quote! { Ok(()) } } else {
580 quote! { Ok(Default::default()) }
584 };
585
586 let match_expr = if handlers.len() == 1 {
588 let err_pattern = if let Some(exc_var) = &handlers[0].name {
590 let exc_ident = safe_ident(exc_var);
591 quote! { Err(#exc_ident) }
592 } else {
593 quote! { Err(_) }
594 };
595
596 let handler_code = &handler_tokens[0];
597
598 quote! {
599 match (|| -> Result<#return_type_tokens, Box<dyn std::error::Error>> {
600 #(#try_stmts_transformed)*
601 #closure_fallback
602 })() {
603 Ok(#ok_value) => { #ok_arm_body },
604 #err_pattern => { #handler_code }
605 }
606 }
607 } else {
608 let exc_var_opt = handlers.iter().find_map(|h| h.name.as_ref());
611 let handler_code = if let Some(idx) = handlers.iter().position(|h| h.name.is_some())
612 {
613 &handler_tokens[idx]
614 } else {
615 &handler_tokens[0]
616 };
617
618 if let Some(exc_var) = exc_var_opt {
619 let exc_ident = safe_ident(exc_var);
620 quote! {
621 match (|| -> Result<#return_type_tokens, Box<dyn std::error::Error>> {
622 #(#try_stmts_transformed)*
623 #closure_fallback
624 })() {
625 Ok(#ok_value) => { #ok_arm_body },
626 Err(#exc_ident) => { #handler_code }
627 }
628 }
629 } else {
630 quote! {
631 match (|| -> Result<#return_type_tokens, Box<dyn std::error::Error>> {
632 #(#try_stmts_transformed)*
633 #closure_fallback
634 })() {
635 Ok(#ok_value) => { #ok_arm_body },
636 Err(_) => { #handler_code }
637 }
638 }
639 }
640 };
641
642 if let Some(finally_code) = finally_stmts {
646 Ok(quote! {
647 #(#hoisted_decls)*
648 {
649 #match_expr
650 #finally_code
651 }
652 })
653 } else {
654 Ok(quote! {
655 #(#hoisted_decls)*
656 #match_expr
657 })
658 }
659 }
660 }
661}
662
663fn extract_parse_from_tokens(
669 try_stmts: &[proc_macro2::TokenStream],
670) -> Option<(String, String, Vec<proc_macro2::TokenStream>)> {
671 if try_stmts.is_empty() {
672 return None;
673 }
674
675 let first_token_stream = &try_stmts[0];
676
677 let stmt: syn::Stmt = syn::parse2(first_token_stream.clone()).ok()?;
679
680 match stmt {
681 syn::Stmt::Local(local) => {
683 let var_name = if let syn::Pat::Ident(pat_ident) = &local.pat {
685 pat_ident.ident.to_string()
686 } else {
687 return None;
688 };
689
690 if let Some(init) = &local.init {
692 let parse_expr = extract_parse_expr(&init.expr)?;
693 let remaining = try_stmts[1..].to_vec();
694 return Some((var_name, parse_expr, remaining));
695 }
696 }
697 syn::Stmt::Expr(expr, _) => {
699 if let syn::Expr::Assign(assign) = expr {
700 let var_name = if let syn::Expr::Path(path) = *assign.left {
702 path.path.segments.last()?.ident.to_string()
703 } else {
704 return None;
705 };
706
707 let parse_expr = extract_parse_expr(&assign.right)?;
708 let remaining = try_stmts[1..].to_vec();
709 return Some((var_name, parse_expr, remaining));
710 }
711 }
712 _ => return None,
715 }
716
717 None
718}
719
720fn extract_parse_expr(expr: &syn::Expr) -> Option<String> {
723 if let syn::Expr::MethodCall(unwrap_call) = expr {
727 if unwrap_call.method != "unwrap_or_default" {
728 return None;
729 }
730
731 if let syn::Expr::MethodCall(parse_call) = &*unwrap_call.receiver {
733 if parse_call.method == "parse" {
734 return Some(quote::quote!(#parse_call).to_string());
737 }
738 }
739 }
740 None
741}
742
743fn extract_accessed_subcommand_fields(
756 body: &[HirStmt],
757 args_var: &str,
758 dest_field: &str,
759 cmd_name: &str,
760 ctx: &CodeGenContext,
761) -> Vec<String> {
762 let mut fields = std::collections::HashSet::new();
763 extract_fields_recursive(body, args_var, dest_field, &mut fields);
764
765 let subcommand_arg_names: std::collections::HashSet<String> = ctx
772 .argparser_tracker
773 .subcommands
774 .values()
775 .filter(|sub| sub.name == cmd_name)
776 .max_by_key(|sub| sub.arguments.len())
777 .map(|sub| {
778 sub.arguments
779 .iter()
780 .map(|arg| {
781 arg.dest.clone().unwrap_or_else(|| {
783 if arg.is_positional {
785 arg.name.clone()
786 } else if let Some(long) = &arg.long {
787 long.trim_start_matches("--").replace('-', "_")
788 } else {
789 arg.name.trim_start_matches('-').replace('-', "_")
790 }
791 })
792 })
793 .collect()
794 })
795 .unwrap_or_default();
796
797 let mut result: Vec<_> = fields
798 .into_iter()
799 .filter(|f| subcommand_arg_names.contains(f))
800 .collect();
801 result.sort(); result
803}
804
805pub(crate) fn extract_fields_recursive(
812 stmts: &[HirStmt],
813 args_var: &str,
814 dest_field: &str,
815 fields: &mut std::collections::HashSet<String>,
816) {
817 for stmt in stmts {
818 match stmt {
819 HirStmt::Expr(expr) => extract_fields_from_expr(expr, args_var, dest_field, fields),
820 HirStmt::Assign { value, .. } => {
821 extract_fields_from_expr(value, args_var, dest_field, fields)
822 }
823 HirStmt::If {
824 condition,
825 then_body,
826 else_body,
827 } => {
828 extract_fields_from_expr(condition, args_var, dest_field, fields);
831 extract_fields_recursive(then_body, args_var, dest_field, fields);
832 if let Some(else_stmts) = else_body {
833 extract_fields_recursive(else_stmts, args_var, dest_field, fields);
834 }
835 }
836 HirStmt::While {
838 condition,
839 body: loop_body,
840 } => {
841 extract_fields_from_expr(condition, args_var, dest_field, fields);
842 extract_fields_recursive(loop_body, args_var, dest_field, fields);
843 }
844 HirStmt::For {
846 iter,
847 body: loop_body,
848 ..
849 } => {
850 extract_fields_from_expr(iter, args_var, dest_field, fields);
851 extract_fields_recursive(loop_body, args_var, dest_field, fields);
852 }
853 HirStmt::Try {
854 body: try_body,
855 handlers,
856 orelse,
857 finalbody,
858 } => {
859 extract_fields_recursive(try_body, args_var, dest_field, fields);
860 for handler in handlers {
861 extract_fields_recursive(&handler.body, args_var, dest_field, fields);
862 }
863 if let Some(orelse_stmts) = orelse {
864 extract_fields_recursive(orelse_stmts, args_var, dest_field, fields);
865 }
866 if let Some(finally_stmts) = finalbody {
867 extract_fields_recursive(finally_stmts, args_var, dest_field, fields);
868 }
869 }
870 HirStmt::With {
871 context,
872 body: with_body,
873 ..
874 } => {
875 extract_fields_from_expr(context, args_var, dest_field, fields);
879 extract_fields_recursive(with_body, args_var, dest_field, fields);
880 }
881 _ => {}
882 }
883 }
884}
885
886pub(crate) fn extract_fields_from_expr(
894 expr: &HirExpr,
895 args_var: &str,
896 dest_field: &str,
897 fields: &mut std::collections::HashSet<String>,
898) {
899 match expr {
900 HirExpr::Attribute { value, attr } => {
902 if let HirExpr::Var(var) = value.as_ref() {
903 if var == args_var {
904 if attr != dest_field {
908 fields.insert(attr.clone());
909 }
910 }
911 }
912 }
913 HirExpr::Call {
915 args: call_args, ..
916 } => {
917 for arg in call_args {
918 extract_fields_from_expr(arg, args_var, dest_field, fields);
919 }
920 }
921 HirExpr::Binary { left, right, .. } => {
922 extract_fields_from_expr(left, args_var, dest_field, fields);
923 extract_fields_from_expr(right, args_var, dest_field, fields);
924 }
925 HirExpr::Unary { operand, .. } => {
926 extract_fields_from_expr(operand, args_var, dest_field, fields);
927 }
928 HirExpr::IfExpr { test, body, orelse } => {
929 extract_fields_from_expr(test, args_var, dest_field, fields);
930 extract_fields_from_expr(body, args_var, dest_field, fields);
931 extract_fields_from_expr(orelse, args_var, dest_field, fields);
932 }
933 HirExpr::Index { base, index } => {
934 extract_fields_from_expr(base, args_var, dest_field, fields);
935 extract_fields_from_expr(index, args_var, dest_field, fields);
936 }
937 HirExpr::List(elements) | HirExpr::Tuple(elements) | HirExpr::Set(elements) => {
938 for elem in elements {
939 extract_fields_from_expr(elem, args_var, dest_field, fields);
940 }
941 }
942 HirExpr::Dict(pairs) => {
943 for (key, value) in pairs {
944 extract_fields_from_expr(key, args_var, dest_field, fields);
945 extract_fields_from_expr(value, args_var, dest_field, fields);
946 }
947 }
948 HirExpr::MethodCall {
949 object,
950 args: method_args,
951 ..
952 } => {
953 extract_fields_from_expr(object, args_var, dest_field, fields);
954 for arg in method_args {
955 extract_fields_from_expr(arg, args_var, dest_field, fields);
956 }
957 }
958 HirExpr::FString { parts } => {
960 for part in parts {
961 if let crate::hir::FStringPart::Expr(expr) = part {
962 extract_fields_from_expr(expr, args_var, dest_field, fields);
963 }
964 }
965 }
966 _ => {}
967 }
968}
969
970pub(crate) fn try_generate_subcommand_match(
992 condition: &HirExpr,
993 then_body: &[HirStmt],
994 else_body: &Option<Vec<HirStmt>>,
995 ctx: &mut CodeGenContext,
996) -> Result<Option<proc_macro2::TokenStream>> {
997 use quote::{format_ident, quote};
998
999 let dest_field = ctx
1002 .argparser_tracker
1003 .subparsers
1004 .values()
1005 .next()
1006 .map(|sp| sp.dest_field.clone())
1007 .unwrap_or_else(|| "command".to_string()); let command_name = match is_subcommand_check(condition, &dest_field, ctx) {
1011 Some(name) => name,
1012 None => return Ok(None),
1013 };
1014
1015 let mut branches = vec![(command_name, then_body)];
1017
1018 let mut current_else = else_body;
1020 while let Some(else_stmts) = current_else {
1021 let (elif_stmt, cse_cmd_name) = if else_stmts.len() == 1 {
1025 (&else_stmts[0], None)
1027 } else if else_stmts.len() == 2 {
1028 if let HirStmt::Assign {
1031 target: AssignTarget::Symbol(var),
1032 value,
1033 ..
1034 } = &else_stmts[0]
1035 {
1036 if var.starts_with("_cse_temp") {
1037 let cmd_name = is_subcommand_check(value, &dest_field, ctx);
1039 (&else_stmts[1], cmd_name)
1040 } else {
1041 break;
1043 }
1044 } else {
1045 break;
1047 }
1048 } else {
1049 break;
1051 };
1052
1053 if let HirStmt::If {
1055 condition: elif_cond,
1056 then_body: elif_then,
1057 else_body: elif_else,
1058 } = elif_stmt
1059 {
1060 let elif_name =
1062 cse_cmd_name.or_else(|| is_subcommand_check(elif_cond, &dest_field, ctx));
1063
1064 if let Some(name) = elif_name {
1065 branches.push((name, elif_then.as_slice()));
1066 current_else = elif_else;
1067 continue;
1068 }
1069 }
1070
1071 break;
1073 }
1074
1075 let has_early_return = branches.iter().any(|(_, body)| {
1078 body.iter()
1079 .any(|stmt| matches!(stmt, HirStmt::Return { .. }))
1080 });
1081
1082 let arms: Vec<proc_macro2::TokenStream> = branches
1085 .iter()
1086 .filter(|(cmd_name, _)| !cmd_name.is_empty())
1087 .map(|(cmd_name, body)| {
1088 let variant_name = format_ident!("{}", to_pascal_case(cmd_name));
1090
1091 let mut accessed_fields =
1096 extract_accessed_subcommand_fields(body, "args", &dest_field, cmd_name, ctx);
1097
1098 let calls_cmd_handler = body.iter().any(|stmt| {
1102 if let HirStmt::Expr(HirExpr::Call { func: func_name, args: call_args, .. }) = stmt {
1103 let is_handler = func_name.starts_with("cmd_") || func_name.starts_with("handle_");
1106 let has_args_param = call_args.iter().any(|a| matches!(a, HirExpr::Var(v) if v == "args"));
1107 is_handler && has_args_param
1108 } else {
1109 false
1110 }
1111 });
1112
1113 if calls_cmd_handler && accessed_fields.is_empty() {
1114 if let Some(subcommand) = ctx
1116 .argparser_tracker
1117 .subcommands
1118 .values()
1119 .filter(|sc| sc.name == *cmd_name)
1120 .max_by_key(|sc| sc.arguments.len())
1121 {
1122 for arg in &subcommand.arguments {
1123 let field_name = arg.rust_field_name();
1127 accessed_fields.push(field_name);
1128 }
1129 }
1130 }
1131
1132 let was_in_match_arm = ctx.in_subcommand_match_arm;
1138 let old_match_fields = std::mem::take(&mut ctx.subcommand_match_fields);
1139 if calls_cmd_handler {
1140 ctx.in_subcommand_match_arm = true;
1141 }
1142 if !accessed_fields.is_empty() {
1144 ctx.subcommand_match_fields = accessed_fields.clone();
1145 }
1146
1147 ctx.enter_scope();
1149
1150 for field_name in &accessed_fields {
1155 if let Some(subcommand) = ctx
1156 .argparser_tracker
1157 .subcommands
1158 .values()
1159 .filter(|sc| sc.name == *cmd_name)
1160 .max_by_key(|sc| sc.arguments.len())
1161 {
1162 if let Some(arg) = subcommand.arguments.iter().find(|a| {
1163 let arg_name = a.long.as_ref()
1165 .map(|s| s.trim_start_matches('-').to_string())
1166 .unwrap_or_else(|| a.name.trim_start_matches('-').to_string());
1167 &arg_name == field_name
1168 }) {
1169 let base_type = if let Some(ref ty) = arg.arg_type {
1171 Some(ty.clone())
1172 } else if matches!(arg.action.as_deref(), Some("store_true") | Some("store_false")) {
1173 Some(Type::Bool)
1174 } else {
1175 None
1176 };
1177
1178 if let Some(ty) = base_type {
1179 let is_bool_flag = matches!(arg.action.as_deref(), Some("store_true") | Some("store_false"));
1183 let is_option_type = !arg.is_positional
1184 && !arg.required.unwrap_or(false)
1185 && arg.default.is_none()
1186 && !is_bool_flag;
1187
1188 let actual_type = if is_option_type {
1189 Type::Optional(Box::new(ty))
1190 } else {
1191 ty
1192 };
1193 ctx.var_types.insert(field_name.clone(), actual_type);
1194 }
1195 }
1196 }
1197 }
1198
1199 let body_stmts: Vec<_> = body
1200 .iter()
1201 .filter_map(|s| {
1202 match s.to_rust_tokens(ctx) {
1203 Ok(tokens) => Some(tokens),
1204 Err(e) => {
1205 tracing::warn!("argparse body stmt conversion failed: {}", e);
1207 None
1208 }
1209 }
1210 })
1211 .collect();
1212 ctx.exit_scope();
1213
1214 ctx.in_subcommand_match_arm = was_in_match_arm;
1216 ctx.subcommand_match_fields = old_match_fields;
1217
1218 if accessed_fields.is_empty() {
1225 quote! {
1228 Some(Commands::#variant_name { .. }) => {
1229 #(#body_stmts)*
1230 }
1231 }
1232 } else {
1233 let _field_idents: Vec<syn::Ident> = accessed_fields
1236 .iter()
1237 .map(|f| format_ident!("{}", f))
1238 .collect();
1239 let ref_field_patterns: Vec<proc_macro2::TokenStream> = accessed_fields
1242 .iter()
1243 .map(|f| {
1244 let ident = safe_ident(f);
1245 quote! { ref #ident }
1246 })
1247 .collect();
1248
1249 let field_bindings: Vec<proc_macro2::TokenStream> = accessed_fields
1257 .iter()
1258 .map(|field_name| {
1259 let field_ident = safe_ident(field_name);
1260
1261 let maybe_arg = ctx
1265 .argparser_tracker
1266 .subcommands
1267 .values()
1268 .filter(|sc| sc.name == *cmd_name)
1269 .max_by_key(|sc| sc.arguments.len())
1270 .and_then(|sc| {
1271 sc.arguments.iter().find(|arg| {
1272 let arg_field_name = arg
1275 .long
1276 .as_ref()
1277 .map(|s| s.trim_start_matches('-').to_string())
1278 .unwrap_or_else(|| arg.name.trim_start_matches('-').to_string());
1279 arg_field_name == *field_name
1280 })
1281 });
1282
1283 let field_type = maybe_arg
1285 .and_then(|arg| {
1286 if let Some(ref base_type) = arg.arg_type {
1288 let is_multi = matches!(
1292 arg.nargs.as_deref(),
1293 Some("+") | Some("*")
1294 );
1295 if is_multi {
1296 return Some(Type::List(Box::new(base_type.clone())));
1297 }
1298 return Some(base_type.clone());
1299 }
1300 if matches!(
1302 arg.action.as_deref(),
1303 Some("store_true") | Some("store_false") | Some("store_const")
1304 ) {
1305 return Some(Type::Bool);
1306 }
1307 None
1308 })
1309 .or_else(|| {
1310 let field_lower = field_name.to_lowercase();
1313 let bool_indicators = [
1314 "binary",
1315 "append",
1316 "verbose",
1317 "quiet",
1318 "force",
1319 "dry_run",
1320 "recursive",
1321 "debug",
1322 "silent",
1323 "capture",
1324 "overwrite",
1325 ];
1326 if bool_indicators
1327 .iter()
1328 .any(|ind| field_lower == *ind || field_lower.ends_with(ind))
1329 {
1330 Some(Type::Bool)
1331 } else {
1332 None
1333 }
1334 });
1335
1336 match field_type {
1341 Some(Type::Bool) => {
1342 quote! { let #field_ident = *#field_ident; }
1344 }
1345 Some(Type::Int) | Some(Type::Float) => {
1346 let has_default = maybe_arg
1351 .as_ref()
1352 .map(|a| a.default.is_some())
1353 .unwrap_or(false);
1354 let is_required = maybe_arg
1355 .as_ref()
1356 .map(|a| a.required.unwrap_or(false))
1357 .unwrap_or(false);
1358 let is_positional = maybe_arg
1359 .as_ref()
1360 .map(|a| a.is_positional)
1361 .unwrap_or(false);
1362 let is_option_without_default = !is_required && !is_positional && !has_default;
1364
1365 if has_default {
1366 let default_expr_opt = maybe_arg
1369 .and_then(|a| a.default.clone());
1370
1371 let default_val = if let Some(ref d) = default_expr_opt {
1372 d.to_rust_expr(ctx).ok()
1373 } else {
1374 None
1375 }.unwrap_or_else(|| {
1376 if matches!(field_type, Some(Type::Float)) {
1378 syn::parse_quote! { 0.0 }
1379 } else {
1380 syn::parse_quote! { 0 }
1381 }
1382 });
1383 quote! { let #field_ident = #field_ident.unwrap_or(#default_val); }
1384 } else if is_option_without_default {
1385 quote! { let #field_ident = #field_ident.clone(); }
1388 } else {
1389 quote! { let #field_ident = *#field_ident; }
1391 }
1392 }
1393 Some(Type::String) => {
1394 let has_default = maybe_arg
1397 .as_ref()
1398 .map(|a| a.default.is_some())
1399 .unwrap_or(false);
1400 let is_required = maybe_arg
1401 .as_ref()
1402 .map(|a| a.required.unwrap_or(false))
1403 .unwrap_or(false);
1404 let is_positional = maybe_arg
1405 .as_ref()
1406 .map(|a| a.is_positional)
1407 .unwrap_or(false);
1408 let is_option_without_default =
1409 !is_required && !is_positional && !has_default;
1410
1411 let field_lower = field_name.to_lowercase();
1413 let owned_indicators = [
1414 "file", "path", "filepath", "input", "output", "dir",
1415 "directory",
1416 ];
1417 let borrowed_indicators =
1418 ["content", "pattern", "text", "message", "data", "value"];
1419
1420 let needs_owned = owned_indicators.iter().any(|ind| {
1421 field_lower == *ind
1422 || field_lower.ends_with(ind)
1423 || field_lower.starts_with(ind)
1424 });
1425 let needs_borrowed = borrowed_indicators.iter().any(|ind| {
1426 field_lower == *ind
1427 || field_lower.ends_with(ind)
1428 || field_lower.starts_with(ind)
1429 });
1430
1431 if is_option_without_default || has_default {
1432 quote! { let #field_ident = #field_ident.as_deref().unwrap_or_default(); }
1436 } else {
1437 if needs_borrowed {
1439 quote! {}
1441 } else if needs_owned {
1442 quote! { let #field_ident = #field_ident.to_string(); }
1444 } else {
1445 quote! { let #field_ident = #field_ident.to_string(); }
1447 }
1448 }
1449 }
1450 Some(Type::Optional(_))
1451 | Some(Type::List(_))
1452 | Some(Type::Dict(_, _)) => {
1453 quote! { let #field_ident = #field_ident.clone(); }
1455 }
1456 None => {
1457 let has_default = maybe_arg
1459 .as_ref()
1460 .map(|a| a.default.is_some())
1461 .unwrap_or(false);
1462 let is_required = maybe_arg
1463 .as_ref()
1464 .map(|a| a.required.unwrap_or(false))
1465 .unwrap_or(false);
1466 let is_positional = maybe_arg
1467 .as_ref()
1468 .map(|a| a.is_positional)
1469 .unwrap_or(false);
1470 let is_option_without_default =
1471 !is_required && !is_positional && !has_default;
1472
1473 let field_lower = field_name.to_lowercase();
1475 let owned_indicators = [
1476 "file",
1477 "path",
1478 "filepath",
1479 "input",
1480 "output",
1481 "dir",
1482 "directory",
1483 ];
1484 let borrowed_indicators =
1485 ["content", "pattern", "text", "message", "data", "value"];
1486 let string_indicators = [
1488 "str", "string", "name", "line", "word", "char", "cmd",
1489 "url", "uri", "host", "token", "key", "id", "code",
1490 "hex", "oct", "suffix", ];
1493 let numeric_indicators = [
1496 "x1", "x2", "y1", "y2", "z1", "z2",
1497 "val", "num", "count", "rate", "coef", "factor",
1498 "min", "max", "sum", "avg", "mean", "std",
1499 "width", "height", "size", "len", "length",
1500 "alpha", "beta", "gamma", "theta", "lr",
1501 ];
1502
1503 let needs_owned = owned_indicators.iter().any(|ind| {
1504 field_lower == *ind
1505 || field_lower.ends_with(ind)
1506 || field_lower.starts_with(ind)
1507 });
1508 let needs_borrowed = borrowed_indicators.iter().any(|ind| {
1509 field_lower == *ind
1510 || field_lower.ends_with(ind)
1511 || field_lower.starts_with(ind)
1512 });
1513 let looks_like_string = string_indicators.iter().any(|ind| {
1515 field_lower == *ind
1516 || field_lower.ends_with(ind)
1517 || field_lower.starts_with(ind)
1518 || field_lower.contains(ind)
1519 });
1520 let needs_numeric_unwrap = !looks_like_string
1522 && numeric_indicators.iter().any(|ind| {
1523 field_lower == *ind
1524 || field_lower.ends_with(ind)
1525 || field_lower.starts_with(ind)
1526 });
1527
1528 if is_option_without_default && looks_like_string {
1531 quote! { let #field_ident = #field_ident.as_deref().unwrap_or_default(); }
1532 } else if is_option_without_default && (needs_owned || needs_borrowed) {
1533 quote! { let #field_ident = #field_ident.as_deref().unwrap_or_default(); }
1535 } else if needs_borrowed || (is_positional && needs_owned) {
1536 quote! {}
1541 } else if needs_owned || looks_like_string {
1542 quote! { let #field_ident = #field_ident.to_string(); }
1544 } else if needs_numeric_unwrap {
1545 quote! { let #field_ident = #field_ident.unwrap_or_default(); }
1548 } else {
1549 quote! {}
1551 }
1552 }
1553 _ => {
1554 quote! { let #field_ident = #field_ident.clone(); }
1556 }
1557 }
1558 })
1559 .collect();
1560
1561 quote! {
1565 Some(Commands::#variant_name { #(#ref_field_patterns,)* .. }) => {
1566 #(#field_bindings)*
1567 #(#body_stmts)*
1568 }
1569 }
1570 }
1571 })
1572 .collect();
1573
1574 Ok(Some(if has_early_return {
1583 quote! {
1585 match &args.command {
1586 #(#arms)*
1587 _ => {}
1588 }
1589 }
1590 } else {
1591 quote! {
1593 match &args.command {
1594 #(#arms)*
1595 _ => unreachable!("Other command variants handled elsewhere")
1596 }
1597 }
1598 }))
1599}
1600
1601pub(crate) fn is_subcommand_check(
1606 expr: &HirExpr,
1607 dest_field: &str,
1608 ctx: &CodeGenContext,
1609) -> Option<String> {
1610 match expr {
1611 HirExpr::Binary {
1613 op: BinOp::Eq,
1614 left,
1615 right,
1616 } => {
1617 let is_dest_field_attr = matches!(
1620 left.as_ref(),
1621 HirExpr::Attribute { attr, .. } if attr == dest_field
1622 );
1623
1624 if is_dest_field_attr {
1626 if let HirExpr::Literal(Literal::String(cmd_name)) = right.as_ref() {
1627 return Some(cmd_name.clone());
1628 }
1629 }
1630 None
1631 }
1632 HirExpr::Var(var_name) => {
1635 ctx.cse_subcommand_temps.get(var_name).cloned()
1637 }
1638 _ => None,
1639 }
1640}
1641
1642impl RustCodeGen for HirStmt {
1643 fn to_rust_tokens(&self, ctx: &mut CodeGenContext) -> Result<proc_macro2::TokenStream> {
1644 match self {
1645 HirStmt::Assign {
1646 target,
1647 value,
1648 type_annotation,
1649 } => codegen_assign_stmt(target, value, type_annotation, ctx),
1650 HirStmt::Return(expr) => codegen_return_stmt(expr, ctx),
1651 HirStmt::If {
1652 condition,
1653 then_body,
1654 else_body,
1655 } => codegen_if_stmt(condition, then_body, else_body, ctx),
1656 HirStmt::While { condition, body } => codegen_while_stmt(condition, body, ctx),
1657 HirStmt::For { target, iter, body } => codegen_for_stmt(target, iter, body, ctx),
1658 HirStmt::Expr(expr) => codegen_expr_stmt(expr, ctx),
1659 HirStmt::Raise {
1660 exception,
1661 cause: _,
1662 } => codegen_raise_stmt(exception, ctx),
1663 HirStmt::Break { label } => codegen_break_stmt(label),
1664 HirStmt::Continue { label } => codegen_continue_stmt(label),
1665 HirStmt::With {
1666 context,
1667 target,
1668 body,
1669 is_async,
1670 } => codegen_with_stmt(context, target, body, *is_async, ctx),
1671 HirStmt::Try {
1672 body,
1673 handlers,
1674 orelse: _,
1675 finalbody,
1676 } => codegen_try_stmt(body, handlers, finalbody, ctx),
1677 HirStmt::Assert { test, msg } => codegen_assert_stmt(test, msg, ctx),
1678 HirStmt::Pass => codegen_pass_stmt(),
1679 HirStmt::Block(stmts) => {
1681 let mut tokens = proc_macro2::TokenStream::new();
1682 for stmt in stmts {
1683 tokens.extend(stmt.to_rust_tokens(ctx)?);
1684 }
1685 Ok(tokens)
1686 }
1687 HirStmt::FunctionDef {
1688 name,
1689 params,
1690 ret_type,
1691 body,
1692 docstring: _,
1693 } => codegen_nested_function_def(name, params, ret_type, body, ctx),
1694 }
1695 }
1696}
1697
1698fn codegen_nested_function_def(
1730 name: &str,
1731 params: &[HirParam],
1732 ret_type: &Type,
1733 body: &[HirStmt],
1734 ctx: &mut CodeGenContext,
1735) -> Result<proc_macro2::TokenStream> {
1736 use quote::quote;
1737
1738 let fn_name = safe_ident(name);
1743
1744 let effective_params: Vec<HirParam> = ctx
1747 .nested_function_params
1748 .get(name)
1749 .cloned()
1750 .unwrap_or_else(|| params.to_vec());
1751
1752 for param in &effective_params {
1756 ctx.var_types.insert(param.name.clone(), param.ty.clone());
1757 }
1758
1759 let param_tokens: Vec<proc_macro2::TokenStream> = effective_params
1764 .iter()
1765 .map(|p| {
1766 let param_name = safe_ident(&p.name);
1767 let param_type = hir_type_to_tokens(&p.ty);
1768
1769 if matches!(p.ty, Type::Dict(_, _) | Type::List(_) | Type::Set(_)) {
1774 quote! { #param_name: &#param_type }
1775 } else if matches!(p.ty, Type::String) {
1776 quote! { #param_name: &str }
1779 } else {
1780 quote! { #param_name: #param_type }
1781 }
1782 })
1783 .collect();
1784
1785 let return_type = hir_type_to_tokens(ret_type);
1787
1788 let saved_can_fail = ctx.current_function_can_fail;
1792 ctx.current_function_can_fail = false;
1793
1794 let saved_return_type = ctx.current_return_type.take();
1798 ctx.current_return_type = Some(ret_type.clone());
1799
1800 let saved_is_main = ctx.is_main_function;
1804 ctx.is_main_function = false;
1805
1806 ctx.enter_scope();
1811
1812 for param in &effective_params {
1814 ctx.declare_var(¶m.name);
1815 }
1816
1817 let saved_mutable_vars = ctx.mutable_vars.clone();
1824 crate::rust_gen::analyze_mutable_vars(body, ctx, &effective_params);
1825
1826 let closure_param_names: std::collections::HashSet<String> =
1830 effective_params.iter().map(|p| p.name.clone()).collect();
1831 let closure_is_fn_mut = ctx.mutable_vars.iter().any(|var| {
1832 !closure_param_names.contains(var)
1833 && !body.iter().any(|stmt| {
1834 matches!(stmt, HirStmt::Assign { target: AssignTarget::Symbol(n), .. } if n == var)
1836 })
1837 });
1838
1839 propagate_return_type_to_vars(body, &mut ctx.var_types, ret_type);
1843
1844 let body_tokens: Vec<proc_macro2::TokenStream> = body
1846 .iter()
1847 .map(|stmt| stmt.to_rust_tokens(ctx))
1848 .collect::<Result<Vec<_>>>()?;
1849
1850 ctx.exit_scope();
1852
1853 ctx.mutable_vars = saved_mutable_vars;
1858
1859 ctx.current_function_can_fail = saved_can_fail;
1861
1862 ctx.current_return_type = saved_return_type;
1864 ctx.is_main_function = saved_is_main;
1865
1866 let is_recursive = is_nested_function_recursive(name, body);
1871
1872 let outer_vars: std::collections::HashSet<String> = ctx
1875 .declared_vars
1876 .iter()
1877 .flat_map(|scope| scope.iter().cloned())
1878 .collect();
1879 let has_captures = captures_outer_scope(params, body, &outer_vars);
1880
1881 let is_declared = ctx.is_declared(name);
1894
1895 if is_recursive && !has_captures {
1899 ctx.declare_var(name);
1901 return Ok(if matches!(ret_type, Type::Unknown) {
1902 quote! {
1903 fn #fn_name(#(#param_tokens),*) {
1904 #(#body_tokens)*
1905 }
1906 }
1907 } else {
1908 quote! {
1909 fn #fn_name(#(#param_tokens),*) -> #return_type {
1910 #(#body_tokens)*
1911 }
1912 }
1913 });
1914 }
1915
1916 ctx.declare_var(name);
1918
1919 let mutability = if closure_is_fn_mut {
1928 quote! { mut }
1929 } else {
1930 quote! {}
1931 };
1932 Ok(if matches!(ret_type, Type::Unknown) {
1933 if is_declared {
1934 quote! {
1935 #fn_name = move |#(#param_tokens),*| {
1936 #(#body_tokens)*
1937 };
1938 }
1939 } else {
1940 quote! {
1941 let #mutability #fn_name = move |#(#param_tokens),*| {
1942 #(#body_tokens)*
1943 };
1944 }
1945 }
1946 } else if is_declared {
1947 quote! {
1948 #fn_name = move |#(#param_tokens),*| -> #return_type {
1949 #(#body_tokens)*
1950 };
1951 }
1952 } else {
1953 quote! {
1954 let #mutability #fn_name = move |#(#param_tokens),*| -> #return_type {
1955 #(#body_tokens)*
1956 };
1957 }
1958 })
1959}
1960
1961pub(crate) fn captures_outer_scope(
1966 params: &[crate::hir::HirParam],
1967 body: &[HirStmt],
1968 outer_vars: &std::collections::HashSet<String>,
1969) -> bool {
1970 use crate::hir::HirExpr;
1971
1972 let mut local_vars: std::collections::HashSet<&str> =
1974 params.iter().map(|p| p.name.as_str()).collect();
1975
1976 fn collect_local_vars<'a>(stmt: &'a HirStmt, locals: &mut std::collections::HashSet<&'a str>) {
1978 match stmt {
1979 HirStmt::Assign {
1980 target: crate::hir::AssignTarget::Symbol(name),
1981 ..
1982 } => {
1983 locals.insert(name.as_str());
1984 }
1985 HirStmt::Assign { .. } => {}
1986 HirStmt::For { target, body, .. } => {
1987 if let crate::hir::AssignTarget::Symbol(name) = target {
1988 locals.insert(name.as_str());
1989 }
1990 for s in body {
1991 collect_local_vars(s, locals);
1992 }
1993 }
1994 HirStmt::If {
1995 then_body,
1996 else_body,
1997 ..
1998 } => {
1999 for s in then_body {
2000 collect_local_vars(s, locals);
2001 }
2002 if let Some(eb) = else_body {
2003 for s in eb {
2004 collect_local_vars(s, locals);
2005 }
2006 }
2007 }
2008 HirStmt::While { body, .. } => {
2009 for s in body {
2010 collect_local_vars(s, locals);
2011 }
2012 }
2013 HirStmt::With { body, target, .. } => {
2014 if let Some(t) = target {
2015 locals.insert(t.as_str());
2016 }
2017 for s in body {
2018 collect_local_vars(s, locals);
2019 }
2020 }
2021 HirStmt::Try {
2022 body,
2023 handlers,
2024 orelse,
2025 finalbody,
2026 } => {
2027 for s in body {
2028 collect_local_vars(s, locals);
2029 }
2030 for h in handlers {
2031 if let Some(name) = &h.name {
2032 locals.insert(name.as_str());
2033 }
2034 for s in &h.body {
2035 collect_local_vars(s, locals);
2036 }
2037 }
2038 if let Some(els) = orelse {
2039 for s in els {
2040 collect_local_vars(s, locals);
2041 }
2042 }
2043 if let Some(fin) = finalbody {
2044 for s in fin {
2045 collect_local_vars(s, locals);
2046 }
2047 }
2048 }
2049 HirStmt::FunctionDef { name, .. } => {
2050 locals.insert(name.as_str());
2051 }
2052 HirStmt::Block(stmts) => {
2053 for s in stmts {
2054 collect_local_vars(s, locals);
2055 }
2056 }
2057 _ => {}
2058 }
2059 }
2060
2061 for stmt in body {
2063 collect_local_vars(stmt, &mut local_vars);
2064 }
2065
2066 fn check_expr_for_capture(
2068 expr: &HirExpr,
2069 local_vars: &std::collections::HashSet<&str>,
2070 outer_vars: &std::collections::HashSet<String>,
2071 ) -> bool {
2072 match expr {
2073 HirExpr::Var(name) => {
2074 !local_vars.contains(name.as_str()) && outer_vars.contains(name)
2076 }
2077 HirExpr::Binary { left, right, .. } => {
2078 check_expr_for_capture(left, local_vars, outer_vars)
2079 || check_expr_for_capture(right, local_vars, outer_vars)
2080 }
2081 HirExpr::Unary { operand, .. } => {
2082 check_expr_for_capture(operand, local_vars, outer_vars)
2083 }
2084 HirExpr::Call {
2085 func, args, kwargs, ..
2086 } => {
2087 let captures_func =
2089 !local_vars.contains(func.as_str()) && outer_vars.contains(func);
2090 captures_func
2091 || args
2092 .iter()
2093 .any(|a| check_expr_for_capture(a, local_vars, outer_vars))
2094 || kwargs
2095 .iter()
2096 .any(|(_, v)| check_expr_for_capture(v, local_vars, outer_vars))
2097 }
2098 HirExpr::DynamicCall {
2099 callee,
2100 args,
2101 kwargs,
2102 } => {
2103 check_expr_for_capture(callee, local_vars, outer_vars)
2104 || args
2105 .iter()
2106 .any(|a| check_expr_for_capture(a, local_vars, outer_vars))
2107 || kwargs
2108 .iter()
2109 .any(|(_, v)| check_expr_for_capture(v, local_vars, outer_vars))
2110 }
2111 HirExpr::MethodCall {
2112 object,
2113 args,
2114 kwargs,
2115 ..
2116 } => {
2117 check_expr_for_capture(object, local_vars, outer_vars)
2118 || args
2119 .iter()
2120 .any(|a| check_expr_for_capture(a, local_vars, outer_vars))
2121 || kwargs
2122 .iter()
2123 .any(|(_, v)| check_expr_for_capture(v, local_vars, outer_vars))
2124 }
2125 HirExpr::Attribute { value, .. } => {
2126 check_expr_for_capture(value, local_vars, outer_vars)
2127 }
2128 HirExpr::Index { base, index } => {
2129 check_expr_for_capture(base, local_vars, outer_vars)
2130 || check_expr_for_capture(index, local_vars, outer_vars)
2131 }
2132 HirExpr::IfExpr { test, body, orelse } => {
2133 check_expr_for_capture(test, local_vars, outer_vars)
2134 || check_expr_for_capture(body, local_vars, outer_vars)
2135 || check_expr_for_capture(orelse, local_vars, outer_vars)
2136 }
2137 HirExpr::List(items)
2138 | HirExpr::Tuple(items)
2139 | HirExpr::Set(items)
2140 | HirExpr::FrozenSet(items) => items
2141 .iter()
2142 .any(|i| check_expr_for_capture(i, local_vars, outer_vars)),
2143 HirExpr::Dict(pairs) => pairs.iter().any(|(k, v)| {
2144 check_expr_for_capture(k, local_vars, outer_vars)
2145 || check_expr_for_capture(v, local_vars, outer_vars)
2146 }),
2147 HirExpr::ListComp {
2148 element,
2149 generators,
2150 }
2151 | HirExpr::SetComp {
2152 element,
2153 generators,
2154 }
2155 | HirExpr::GeneratorExp {
2156 element,
2157 generators,
2158 } => {
2159 check_expr_for_capture(element, local_vars, outer_vars)
2160 || generators.iter().any(|g| {
2161 check_expr_for_capture(&g.iter, local_vars, outer_vars)
2162 || g.conditions
2163 .iter()
2164 .any(|c| check_expr_for_capture(c, local_vars, outer_vars))
2165 })
2166 }
2167 HirExpr::DictComp {
2168 key,
2169 value,
2170 generators,
2171 } => {
2172 check_expr_for_capture(key, local_vars, outer_vars)
2173 || check_expr_for_capture(value, local_vars, outer_vars)
2174 || generators.iter().any(|g| {
2175 check_expr_for_capture(&g.iter, local_vars, outer_vars)
2176 || g.conditions
2177 .iter()
2178 .any(|c| check_expr_for_capture(c, local_vars, outer_vars))
2179 })
2180 }
2181 HirExpr::Lambda { body, .. } => check_expr_for_capture(body, local_vars, outer_vars),
2182 HirExpr::Await { value } => check_expr_for_capture(value, local_vars, outer_vars),
2183 HirExpr::Slice {
2184 base,
2185 start,
2186 stop,
2187 step,
2188 } => {
2189 check_expr_for_capture(base, local_vars, outer_vars)
2190 || start
2191 .as_ref()
2192 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars))
2193 || stop
2194 .as_ref()
2195 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars))
2196 || step
2197 .as_ref()
2198 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars))
2199 }
2200 HirExpr::Borrow { expr, .. } => check_expr_for_capture(expr, local_vars, outer_vars),
2201 HirExpr::FString { parts } => parts.iter().any(|p| {
2202 if let crate::hir::FStringPart::Expr(e) = p {
2203 check_expr_for_capture(e, local_vars, outer_vars)
2204 } else {
2205 false
2206 }
2207 }),
2208 HirExpr::Yield { value } => value
2209 .as_ref()
2210 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars)),
2211 HirExpr::SortByKey {
2212 iterable,
2213 key_body,
2214 reverse_expr,
2215 ..
2216 } => {
2217 check_expr_for_capture(iterable, local_vars, outer_vars)
2218 || check_expr_for_capture(key_body, local_vars, outer_vars)
2219 || reverse_expr
2220 .as_ref()
2221 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars))
2222 }
2223 HirExpr::NamedExpr { value, .. } => {
2224 check_expr_for_capture(value, local_vars, outer_vars)
2225 }
2226 _ => false,
2227 }
2228 }
2229
2230 fn check_stmt_for_capture(
2231 stmt: &HirStmt,
2232 local_vars: &std::collections::HashSet<&str>,
2233 outer_vars: &std::collections::HashSet<String>,
2234 ) -> bool {
2235 match stmt {
2236 HirStmt::Expr(expr) | HirStmt::Return(Some(expr)) => {
2237 check_expr_for_capture(expr, local_vars, outer_vars)
2238 }
2239 HirStmt::Assign { value, .. } => check_expr_for_capture(value, local_vars, outer_vars),
2240 HirStmt::If {
2241 condition,
2242 then_body,
2243 else_body,
2244 } => {
2245 check_expr_for_capture(condition, local_vars, outer_vars)
2246 || then_body
2247 .iter()
2248 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2249 || else_body.as_ref().is_some_and(|b| {
2250 b.iter()
2251 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2252 })
2253 }
2254 HirStmt::While { condition, body } => {
2255 check_expr_for_capture(condition, local_vars, outer_vars)
2256 || body
2257 .iter()
2258 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2259 }
2260 HirStmt::For { iter, body, .. } => {
2261 check_expr_for_capture(iter, local_vars, outer_vars)
2262 || body
2263 .iter()
2264 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2265 }
2266 HirStmt::With { context, body, .. } => {
2267 check_expr_for_capture(context, local_vars, outer_vars)
2268 || body
2269 .iter()
2270 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2271 }
2272 HirStmt::Try {
2273 body,
2274 handlers,
2275 orelse,
2276 finalbody,
2277 } => {
2278 body.iter()
2279 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2280 || handlers.iter().any(|h| {
2281 h.body
2282 .iter()
2283 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2284 })
2285 || orelse.as_ref().is_some_and(|b| {
2286 b.iter()
2287 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2288 })
2289 || finalbody.as_ref().is_some_and(|b| {
2290 b.iter()
2291 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars))
2292 })
2293 }
2294 HirStmt::FunctionDef { body, .. } => body
2295 .iter()
2296 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars)),
2297 HirStmt::Block(stmts) => stmts
2298 .iter()
2299 .any(|s| check_stmt_for_capture(s, local_vars, outer_vars)),
2300 HirStmt::Assert { test, msg } => {
2301 check_expr_for_capture(test, local_vars, outer_vars)
2302 || msg
2303 .as_ref()
2304 .is_some_and(|m| check_expr_for_capture(m, local_vars, outer_vars))
2305 }
2306 HirStmt::Raise { exception, cause } => {
2307 exception
2308 .as_ref()
2309 .is_some_and(|e| check_expr_for_capture(e, local_vars, outer_vars))
2310 || cause
2311 .as_ref()
2312 .is_some_and(|c| check_expr_for_capture(c, local_vars, outer_vars))
2313 }
2314 _ => false,
2315 }
2316 }
2317
2318 body.iter()
2319 .any(|stmt| check_stmt_for_capture(stmt, &local_vars, outer_vars))
2320}
2321
2322#[cfg(test)]
2323mod tests {
2324 use super::*;
2325 use crate::hir::*;
2326 use std::collections::HashSet;
2327
2328 #[test]
2331 fn test_extract_fields_attribute_match() {
2332 let mut fields = HashSet::new();
2333 let expr = HirExpr::Attribute {
2334 value: Box::new(HirExpr::Var("args".to_string())),
2335 attr: "name".to_string(),
2336 };
2337 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2338 assert!(fields.contains("name"));
2339 }
2340
2341 #[test]
2342 fn test_extract_fields_skips_dest_field() {
2343 let mut fields = HashSet::new();
2344 let expr = HirExpr::Attribute {
2345 value: Box::new(HirExpr::Var("args".to_string())),
2346 attr: "command".to_string(),
2347 };
2348 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2349 assert!(fields.is_empty());
2350 }
2351
2352 #[test]
2353 fn test_extract_fields_wrong_var_name() {
2354 let mut fields = HashSet::new();
2355 let expr = HirExpr::Attribute {
2356 value: Box::new(HirExpr::Var("config".to_string())),
2357 attr: "name".to_string(),
2358 };
2359 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2360 assert!(fields.is_empty());
2361 }
2362
2363 #[test]
2364 fn test_extract_fields_in_call_args() {
2365 let mut fields = HashSet::new();
2366 let expr = HirExpr::Call {
2367 func: "process".to_string(),
2368 args: vec![HirExpr::Attribute {
2369 value: Box::new(HirExpr::Var("args".to_string())),
2370 attr: "file".to_string(),
2371 }],
2372 kwargs: vec![],
2373 };
2374 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2375 assert!(fields.contains("file"));
2376 }
2377
2378 #[test]
2379 fn test_extract_fields_in_binary_expr() {
2380 let mut fields = HashSet::new();
2381 let expr = HirExpr::Binary {
2382 op: BinOp::Add,
2383 left: Box::new(HirExpr::Attribute {
2384 value: Box::new(HirExpr::Var("args".to_string())),
2385 attr: "host".to_string(),
2386 }),
2387 right: Box::new(HirExpr::Attribute {
2388 value: Box::new(HirExpr::Var("args".to_string())),
2389 attr: "port".to_string(),
2390 }),
2391 };
2392 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2393 assert!(fields.contains("host"));
2394 assert!(fields.contains("port"));
2395 }
2396
2397 #[test]
2398 fn test_extract_fields_in_unary_expr() {
2399 let mut fields = HashSet::new();
2400 let expr = HirExpr::Unary {
2401 op: UnaryOp::Not,
2402 operand: Box::new(HirExpr::Attribute {
2403 value: Box::new(HirExpr::Var("args".to_string())),
2404 attr: "verbose".to_string(),
2405 }),
2406 };
2407 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2408 assert!(fields.contains("verbose"));
2409 }
2410
2411 #[test]
2412 fn test_extract_fields_in_if_expr() {
2413 let mut fields = HashSet::new();
2414 let expr = HirExpr::IfExpr {
2415 test: Box::new(HirExpr::Attribute {
2416 value: Box::new(HirExpr::Var("args".to_string())),
2417 attr: "flag".to_string(),
2418 }),
2419 body: Box::new(HirExpr::Attribute {
2420 value: Box::new(HirExpr::Var("args".to_string())),
2421 attr: "val_a".to_string(),
2422 }),
2423 orelse: Box::new(HirExpr::Attribute {
2424 value: Box::new(HirExpr::Var("args".to_string())),
2425 attr: "val_b".to_string(),
2426 }),
2427 };
2428 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2429 assert_eq!(fields.len(), 3);
2430 assert!(fields.contains("flag"));
2431 assert!(fields.contains("val_a"));
2432 assert!(fields.contains("val_b"));
2433 }
2434
2435 #[test]
2436 fn test_extract_fields_in_index() {
2437 let mut fields = HashSet::new();
2438 let expr = HirExpr::Index {
2439 base: Box::new(HirExpr::Attribute {
2440 value: Box::new(HirExpr::Var("args".to_string())),
2441 attr: "items".to_string(),
2442 }),
2443 index: Box::new(HirExpr::Literal(Literal::Int(0))),
2444 };
2445 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2446 assert!(fields.contains("items"));
2447 }
2448
2449 #[test]
2450 fn test_extract_fields_in_list() {
2451 let mut fields = HashSet::new();
2452 let expr = HirExpr::List(vec![HirExpr::Attribute {
2453 value: Box::new(HirExpr::Var("args".to_string())),
2454 attr: "item".to_string(),
2455 }]);
2456 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2457 assert!(fields.contains("item"));
2458 }
2459
2460 #[test]
2461 fn test_extract_fields_in_tuple() {
2462 let mut fields = HashSet::new();
2463 let expr = HirExpr::Tuple(vec![HirExpr::Attribute {
2464 value: Box::new(HirExpr::Var("args".to_string())),
2465 attr: "x".to_string(),
2466 }]);
2467 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2468 assert!(fields.contains("x"));
2469 }
2470
2471 #[test]
2472 fn test_extract_fields_in_set() {
2473 let mut fields = HashSet::new();
2474 let expr = HirExpr::Set(vec![HirExpr::Attribute {
2475 value: Box::new(HirExpr::Var("args".to_string())),
2476 attr: "tag".to_string(),
2477 }]);
2478 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2479 assert!(fields.contains("tag"));
2480 }
2481
2482 #[test]
2483 fn test_extract_fields_in_dict() {
2484 let mut fields = HashSet::new();
2485 let expr = HirExpr::Dict(vec![(
2486 HirExpr::Literal(Literal::String("key".to_string())),
2487 HirExpr::Attribute {
2488 value: Box::new(HirExpr::Var("args".to_string())),
2489 attr: "value".to_string(),
2490 },
2491 )]);
2492 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2493 assert!(fields.contains("value"));
2494 }
2495
2496 #[test]
2497 fn test_extract_fields_in_method_call() {
2498 let mut fields = HashSet::new();
2499 let expr = HirExpr::MethodCall {
2500 object: Box::new(HirExpr::Attribute {
2501 value: Box::new(HirExpr::Var("args".to_string())),
2502 attr: "data".to_string(),
2503 }),
2504 method: "upper".to_string(),
2505 args: vec![],
2506 kwargs: vec![],
2507 };
2508 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2509 assert!(fields.contains("data"));
2510 }
2511
2512 #[test]
2513 fn test_extract_fields_in_fstring() {
2514 let mut fields = HashSet::new();
2515 let expr = HirExpr::FString {
2516 parts: vec![
2517 crate::hir::FStringPart::Literal("Hello ".to_string()),
2518 crate::hir::FStringPart::Expr(Box::new(HirExpr::Attribute {
2519 value: Box::new(HirExpr::Var("args".to_string())),
2520 attr: "user".to_string(),
2521 })),
2522 ],
2523 };
2524 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2525 assert!(fields.contains("user"));
2526 }
2527
2528 #[test]
2529 fn test_extract_fields_literal_no_match() {
2530 let mut fields = HashSet::new();
2531 let expr = HirExpr::Literal(Literal::Int(42));
2532 extract_fields_from_expr(&expr, "args", "command", &mut fields);
2533 assert!(fields.is_empty());
2534 }
2535
2536 #[test]
2539 fn test_extract_fields_recursive_empty() {
2540 let mut fields = HashSet::new();
2541 extract_fields_recursive(&[], "args", "command", &mut fields);
2542 assert!(fields.is_empty());
2543 }
2544
2545 #[test]
2546 fn test_extract_fields_recursive_expr_stmt() {
2547 let mut fields = HashSet::new();
2548 let stmts = vec![HirStmt::Expr(HirExpr::Attribute {
2549 value: Box::new(HirExpr::Var("args".to_string())),
2550 attr: "output".to_string(),
2551 })];
2552 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2553 assert!(fields.contains("output"));
2554 }
2555
2556 #[test]
2557 fn test_extract_fields_recursive_assign() {
2558 let mut fields = HashSet::new();
2559 let stmts = vec![HirStmt::Assign {
2560 target: AssignTarget::Symbol("x".to_string()),
2561 value: HirExpr::Attribute {
2562 value: Box::new(HirExpr::Var("args".to_string())),
2563 attr: "count".to_string(),
2564 },
2565 type_annotation: None,
2566 }];
2567 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2568 assert!(fields.contains("count"));
2569 }
2570
2571 #[test]
2572 fn test_extract_fields_recursive_if_with_condition() {
2573 let mut fields = HashSet::new();
2574 let stmts = vec![HirStmt::If {
2575 condition: HirExpr::Attribute {
2576 value: Box::new(HirExpr::Var("args".to_string())),
2577 attr: "debug".to_string(),
2578 },
2579 then_body: vec![HirStmt::Expr(HirExpr::Attribute {
2580 value: Box::new(HirExpr::Var("args".to_string())),
2581 attr: "level".to_string(),
2582 })],
2583 else_body: Some(vec![HirStmt::Expr(HirExpr::Attribute {
2584 value: Box::new(HirExpr::Var("args".to_string())),
2585 attr: "quiet".to_string(),
2586 })]),
2587 }];
2588 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2589 assert!(fields.contains("debug"));
2590 assert!(fields.contains("level"));
2591 assert!(fields.contains("quiet"));
2592 }
2593
2594 #[test]
2595 fn test_extract_fields_recursive_while() {
2596 let mut fields = HashSet::new();
2597 let stmts = vec![HirStmt::While {
2598 condition: HirExpr::Attribute {
2599 value: Box::new(HirExpr::Var("args".to_string())),
2600 attr: "running".to_string(),
2601 },
2602 body: vec![],
2603 }];
2604 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2605 assert!(fields.contains("running"));
2606 }
2607
2608 #[test]
2609 fn test_extract_fields_recursive_for() {
2610 let mut fields = HashSet::new();
2611 let stmts = vec![HirStmt::For {
2612 target: AssignTarget::Symbol("item".to_string()),
2613 iter: HirExpr::Attribute {
2614 value: Box::new(HirExpr::Var("args".to_string())),
2615 attr: "items".to_string(),
2616 },
2617 body: vec![],
2618 }];
2619 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2620 assert!(fields.contains("items"));
2621 }
2622
2623 #[test]
2624 fn test_extract_fields_recursive_try() {
2625 let mut fields = HashSet::new();
2626 let stmts = vec![HirStmt::Try {
2627 body: vec![HirStmt::Expr(HirExpr::Attribute {
2628 value: Box::new(HirExpr::Var("args".to_string())),
2629 attr: "path".to_string(),
2630 })],
2631 handlers: vec![ExceptHandler {
2632 exception_type: None,
2633 name: None,
2634 body: vec![HirStmt::Expr(HirExpr::Attribute {
2635 value: Box::new(HirExpr::Var("args".to_string())),
2636 attr: "fallback".to_string(),
2637 })],
2638 }],
2639 orelse: Some(vec![HirStmt::Expr(HirExpr::Attribute {
2640 value: Box::new(HirExpr::Var("args".to_string())),
2641 attr: "success".to_string(),
2642 })]),
2643 finalbody: Some(vec![HirStmt::Expr(HirExpr::Attribute {
2644 value: Box::new(HirExpr::Var("args".to_string())),
2645 attr: "cleanup".to_string(),
2646 })]),
2647 }];
2648 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2649 assert!(fields.contains("path"));
2650 assert!(fields.contains("fallback"));
2651 assert!(fields.contains("success"));
2652 assert!(fields.contains("cleanup"));
2653 }
2654
2655 #[test]
2656 fn test_extract_fields_recursive_with() {
2657 let mut fields = HashSet::new();
2658 let stmts = vec![HirStmt::With {
2659 context: HirExpr::Attribute {
2660 value: Box::new(HirExpr::Var("args".to_string())),
2661 attr: "resource".to_string(),
2662 },
2663 target: Some("r".to_string()),
2664 body: vec![],
2665 is_async: false,
2666 }];
2667 extract_fields_recursive(&stmts, "args", "command", &mut fields);
2668 assert!(fields.contains("resource"));
2669 }
2670
2671 #[test]
2672 fn test_extract_fields_recursive_custom_dest_field() {
2673 let mut fields = HashSet::new();
2674 let stmts = vec![HirStmt::Expr(HirExpr::Attribute {
2675 value: Box::new(HirExpr::Var("args".to_string())),
2676 attr: "action".to_string(),
2677 })];
2678 extract_fields_recursive(&stmts, "args", "action", &mut fields);
2679 assert!(fields.is_empty());
2681 }
2682
2683 #[test]
2686 fn test_is_subcommand_check_direct_match() {
2687 let ctx = CodeGenContext::default();
2688 let expr = HirExpr::Binary {
2689 op: BinOp::Eq,
2690 left: Box::new(HirExpr::Attribute {
2691 value: Box::new(HirExpr::Var("args".to_string())),
2692 attr: "command".to_string(),
2693 }),
2694 right: Box::new(HirExpr::Literal(Literal::String("init".to_string()))),
2695 };
2696 let result = is_subcommand_check(&expr, "command", &ctx);
2697 assert_eq!(result, Some("init".to_string()));
2698 }
2699
2700 #[test]
2701 fn test_is_subcommand_check_custom_dest() {
2702 let ctx = CodeGenContext::default();
2703 let expr = HirExpr::Binary {
2704 op: BinOp::Eq,
2705 left: Box::new(HirExpr::Attribute {
2706 value: Box::new(HirExpr::Var("args".to_string())),
2707 attr: "action".to_string(),
2708 }),
2709 right: Box::new(HirExpr::Literal(Literal::String("deploy".to_string()))),
2710 };
2711 let result = is_subcommand_check(&expr, "action", &ctx);
2712 assert_eq!(result, Some("deploy".to_string()));
2713 }
2714
2715 #[test]
2716 fn test_is_subcommand_check_wrong_dest() {
2717 let ctx = CodeGenContext::default();
2718 let expr = HirExpr::Binary {
2719 op: BinOp::Eq,
2720 left: Box::new(HirExpr::Attribute {
2721 value: Box::new(HirExpr::Var("args".to_string())),
2722 attr: "name".to_string(),
2723 }),
2724 right: Box::new(HirExpr::Literal(Literal::String("test".to_string()))),
2725 };
2726 let result = is_subcommand_check(&expr, "command", &ctx);
2727 assert_eq!(result, None);
2728 }
2729
2730 #[test]
2731 fn test_is_subcommand_check_not_eq_op() {
2732 let ctx = CodeGenContext::default();
2733 let expr = HirExpr::Binary {
2734 op: BinOp::Lt,
2735 left: Box::new(HirExpr::Attribute {
2736 value: Box::new(HirExpr::Var("args".to_string())),
2737 attr: "command".to_string(),
2738 }),
2739 right: Box::new(HirExpr::Literal(Literal::String("init".to_string()))),
2740 };
2741 let result = is_subcommand_check(&expr, "command", &ctx);
2742 assert_eq!(result, None);
2743 }
2744
2745 #[test]
2746 fn test_is_subcommand_check_not_string_literal() {
2747 let ctx = CodeGenContext::default();
2748 let expr = HirExpr::Binary {
2749 op: BinOp::Eq,
2750 left: Box::new(HirExpr::Attribute {
2751 value: Box::new(HirExpr::Var("args".to_string())),
2752 attr: "command".to_string(),
2753 }),
2754 right: Box::new(HirExpr::Literal(Literal::Int(42))),
2755 };
2756 let result = is_subcommand_check(&expr, "command", &ctx);
2757 assert_eq!(result, None);
2758 }
2759
2760 #[test]
2761 fn test_is_subcommand_check_cse_temp() {
2762 let mut ctx = CodeGenContext::default();
2763 ctx.cse_subcommand_temps
2764 .insert("_cse_0".to_string(), "build".to_string());
2765 let expr = HirExpr::Var("_cse_0".to_string());
2766 let result = is_subcommand_check(&expr, "command", &ctx);
2767 assert_eq!(result, Some("build".to_string()));
2768 }
2769
2770 #[test]
2771 fn test_is_subcommand_check_unknown_cse_temp() {
2772 let ctx = CodeGenContext::default();
2773 let expr = HirExpr::Var("_cse_99".to_string());
2774 let result = is_subcommand_check(&expr, "command", &ctx);
2775 assert_eq!(result, None);
2776 }
2777
2778 #[test]
2779 fn test_is_subcommand_check_literal_no_match() {
2780 let ctx = CodeGenContext::default();
2781 let expr = HirExpr::Literal(Literal::Int(42));
2782 let result = is_subcommand_check(&expr, "command", &ctx);
2783 assert_eq!(result, None);
2784 }
2785
2786 #[test]
2789 fn test_captures_outer_scope_no_capture() {
2790 let params = vec![HirParam::new("x".to_string(), Type::Int)];
2791 let body = vec![HirStmt::Return(Some(HirExpr::Var("x".to_string())))];
2792 let outer_vars: HashSet<String> = ["y".to_string()].into_iter().collect();
2793 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
2794 }
2795
2796 #[test]
2797 fn test_captures_outer_scope_uses_outer_var() {
2798 let params = vec![HirParam::new("x".to_string(), Type::Int)];
2799 let body = vec![HirStmt::Return(Some(HirExpr::Binary {
2800 op: BinOp::Add,
2801 left: Box::new(HirExpr::Var("x".to_string())),
2802 right: Box::new(HirExpr::Var("y".to_string())),
2803 }))];
2804 let outer_vars: HashSet<String> = ["y".to_string()].into_iter().collect();
2805 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
2806 }
2807
2808 #[test]
2809 fn test_captures_outer_scope_locally_assigned() {
2810 let params = vec![];
2811 let body = vec![
2812 HirStmt::Assign {
2813 target: AssignTarget::Symbol("y".to_string()),
2814 value: HirExpr::Literal(Literal::Int(10)),
2815 type_annotation: None,
2816 },
2817 HirStmt::Return(Some(HirExpr::Var("y".to_string()))),
2818 ];
2819 let outer_vars: HashSet<String> = ["y".to_string()].into_iter().collect();
2820 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
2822 }
2823
2824 #[test]
2825 fn test_captures_outer_scope_empty_body() {
2826 let params = vec![];
2827 let body = vec![];
2828 let outer_vars: HashSet<String> = ["z".to_string()].into_iter().collect();
2829 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
2830 }
2831
2832 #[test]
2833 fn test_captures_outer_scope_empty_outer() {
2834 let params = vec![];
2835 let body = vec![HirStmt::Expr(HirExpr::Var("x".to_string()))];
2836 let outer_vars: HashSet<String> = HashSet::new();
2837 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
2838 }
2839
2840 fn transpile(python_code: &str) -> String {
2843 use crate::ast_bridge::AstBridge;
2844 use crate::rust_gen::generate_rust_file;
2845 use crate::type_mapper::TypeMapper;
2846 use rustpython_parser::{parse, Mode};
2847
2848 let ast = parse(python_code, Mode::Module, "<test>").expect("parse");
2849 let (module, _) = AstBridge::new()
2850 .with_source(python_code.to_string())
2851 .python_to_hir(ast)
2852 .expect("hir");
2853 let tm = TypeMapper::default();
2854 let (result, _) = generate_rust_file(&module, &tm).expect("codegen");
2855 result
2856 }
2857
2858 #[test]
2859 fn test_transpile_try_except_basic() {
2860 let code = r#"
2861def safe_div(a: int, b: int) -> int:
2862 try:
2863 result = a // b
2864 return result
2865 except ZeroDivisionError:
2866 return 0
2867"#;
2868 let rust = transpile(code);
2869 assert!(rust.contains("fn safe_div"));
2870 }
2871
2872 #[test]
2873 fn test_transpile_try_except_finally() {
2874 let code = r#"
2875def cleanup_action() -> str:
2876 result = ""
2877 try:
2878 result = "ok"
2879 except ValueError:
2880 result = "error"
2881 finally:
2882 result = result + " done"
2883 return result
2884"#;
2885 let rust = transpile(code);
2886 assert!(rust.contains("fn cleanup_action"));
2887 }
2888
2889 #[test]
2890 fn test_transpile_try_except_multiple_handlers() {
2891 let code = r#"
2892def parse_value(s: str) -> int:
2893 try:
2894 return int(s)
2895 except ValueError:
2896 return -1
2897 except TypeError:
2898 return -2
2899"#;
2900 let rust = transpile(code);
2901 assert!(rust.contains("fn parse_value"));
2902 }
2903
2904 #[test]
2905 fn test_transpile_nested_function_with_closure() {
2906 let code = r#"
2907def outer(x: int) -> int:
2908 def inner(y: int) -> int:
2909 return x + y
2910 return inner(10)
2911"#;
2912 let rust = transpile(code);
2913 assert!(rust.contains("fn outer"));
2914 }
2915
2916 #[test]
2919 fn test_transpile_try_bare_except() {
2920 let code = r#"
2921def risky() -> str:
2922 try:
2923 return "ok"
2924 except:
2925 return "error"
2926"#;
2927 let rust = transpile(code);
2928 assert!(rust.contains("fn risky"));
2929 }
2930
2931 #[test]
2932 fn test_transpile_try_else_clause() {
2933 let code = r#"
2934def try_else(x: int) -> str:
2935 try:
2936 result = x + 1
2937 except ValueError:
2938 return "error"
2939 else:
2940 return "ok"
2941 return "done"
2942"#;
2943 let rust = transpile(code);
2944 assert!(rust.contains("fn try_else"));
2945 }
2946
2947 #[test]
2948 fn test_transpile_try_exception_variable() {
2949 let code = r#"
2950def catch_named(s: str) -> str:
2951 try:
2952 return s
2953 except ValueError as e:
2954 return "caught"
2955"#;
2956 let rust = transpile(code);
2957 assert!(rust.contains("fn catch_named"));
2958 }
2959
2960 #[test]
2961 fn test_transpile_try_reraise() {
2962 let code = r#"
2963def reraise(x: int) -> int:
2964 try:
2965 return x
2966 except ValueError:
2967 raise
2968"#;
2969 let rust = transpile(code);
2970 assert!(rust.contains("fn reraise"));
2971 }
2972
2973 #[test]
2974 fn test_transpile_nested_try() {
2975 let code = r#"
2976def nested_try() -> str:
2977 try:
2978 try:
2979 return "inner"
2980 except TypeError:
2981 return "inner error"
2982 except ValueError:
2983 return "outer error"
2984"#;
2985 let rust = transpile(code);
2986 assert!(rust.contains("fn nested_try"));
2987 }
2988
2989 #[test]
2990 fn test_transpile_try_with_return_in_finally() {
2991 let code = r#"
2992def finally_return() -> str:
2993 try:
2994 result = "ok"
2995 except ValueError:
2996 result = "error"
2997 finally:
2998 return result
2999"#;
3000 let rust = transpile(code);
3001 assert!(rust.contains("fn finally_return"));
3002 }
3003
3004 #[test]
3005 fn test_transpile_nested_function_no_capture() {
3006 let code = r#"
3007def outer() -> int:
3008 def inner(x: int) -> int:
3009 return x * 2
3010 return inner(5)
3011"#;
3012 let rust = transpile(code);
3013 assert!(rust.contains("fn outer"));
3014 }
3015
3016 #[test]
3017 fn test_transpile_nested_function_multiple() {
3018 let code = r#"
3019def parent() -> int:
3020 def add(a: int, b: int) -> int:
3021 return a + b
3022 def mul(a: int, b: int) -> int:
3023 return a * b
3024 return add(2, 3) + mul(4, 5)
3025"#;
3026 let rust = transpile(code);
3027 assert!(
3028 rust.contains("fn parent"),
3029 "Should contain parent function: {}",
3030 rust
3031 );
3032 }
3033
3034 #[test]
3035 fn test_transpile_try_with_assignment() {
3036 let code = r#"
3037def parse_int(s: str) -> int:
3038 try:
3039 n = int(s)
3040 return n
3041 except ValueError:
3042 return 0
3043"#;
3044 let rust = transpile(code);
3045 assert!(rust.contains("fn parse_int"));
3046 }
3047
3048 #[test]
3051 fn test_captures_outer_scope_in_for_body() {
3052 let params = vec![HirParam::new("x".to_string(), Type::Int)];
3053 let body = vec![HirStmt::For {
3054 target: AssignTarget::Symbol("i".to_string()),
3055 iter: HirExpr::Var("outer_list".to_string()),
3056 body: vec![HirStmt::Expr(HirExpr::Var("x".to_string()))],
3057 }];
3058 let outer_vars: HashSet<String> = ["outer_list".to_string()].into_iter().collect();
3059 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3060 }
3061
3062 #[test]
3063 fn test_captures_outer_scope_param_shadows_outer() {
3064 let params = vec![HirParam::new("y".to_string(), Type::Int)];
3065 let body = vec![HirStmt::Return(Some(HirExpr::Var("y".to_string())))];
3066 let outer_vars: HashSet<String> = ["y".to_string()].into_iter().collect();
3067 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
3069 }
3070
3071 #[test]
3072 fn test_captures_outer_scope_in_if_condition() {
3073 let params = vec![];
3074 let body = vec![HirStmt::If {
3075 condition: HirExpr::Var("threshold".to_string()),
3076 then_body: vec![],
3077 else_body: None,
3078 }];
3079 let outer_vars: HashSet<String> = ["threshold".to_string()].into_iter().collect();
3080 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3081 }
3082
3083 #[test]
3084 fn test_captures_outer_scope_in_while_condition() {
3085 let params = vec![];
3086 let body = vec![HirStmt::While {
3087 condition: HirExpr::Var("running".to_string()),
3088 body: vec![],
3089 }];
3090 let outer_vars: HashSet<String> = ["running".to_string()].into_iter().collect();
3091 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3092 }
3093
3094 #[test]
3097 fn test_extract_fields_in_comparison() {
3098 let mut fields = HashSet::new();
3099 let expr = HirExpr::Binary {
3100 op: BinOp::Gt,
3101 left: Box::new(HirExpr::Attribute {
3102 value: Box::new(HirExpr::Var("args".to_string())),
3103 attr: "threshold".to_string(),
3104 }),
3105 right: Box::new(HirExpr::Literal(Literal::Int(0))),
3106 };
3107 extract_fields_from_expr(&expr, "args", "command", &mut fields);
3108 assert!(fields.contains("threshold"));
3109 }
3110
3111 #[test]
3112 fn test_extract_fields_nested_attribute_no_match() {
3113 let mut fields = HashSet::new();
3115 let expr = HirExpr::Attribute {
3116 value: Box::new(HirExpr::Attribute {
3117 value: Box::new(HirExpr::Var("args".to_string())),
3118 attr: "config".to_string(),
3119 }),
3120 attr: "name".to_string(),
3121 };
3122 extract_fields_from_expr(&expr, "args", "command", &mut fields);
3123 assert!(fields.is_empty());
3125 }
3126
3127 #[test]
3128 fn test_extract_fields_in_method_args() {
3129 let mut fields = HashSet::new();
3130 let expr = HirExpr::MethodCall {
3131 object: Box::new(HirExpr::Var("obj".to_string())),
3132 method: "process".to_string(),
3133 args: vec![HirExpr::Attribute {
3134 value: Box::new(HirExpr::Var("args".to_string())),
3135 attr: "input".to_string(),
3136 }],
3137 kwargs: vec![],
3138 };
3139 extract_fields_from_expr(&expr, "args", "command", &mut fields);
3140 assert!(fields.contains("input"));
3141 }
3142
3143 #[test]
3146 fn test_extract_fields_recursive_return_stmt_not_handled() {
3147 let mut fields = HashSet::new();
3150 let stmts = vec![HirStmt::Return(Some(HirExpr::Attribute {
3151 value: Box::new(HirExpr::Var("args".to_string())),
3152 attr: "result".to_string(),
3153 }))];
3154 extract_fields_recursive(&stmts, "args", "command", &mut fields);
3155 assert!(fields.is_empty());
3156 }
3157
3158 #[test]
3159 fn test_extract_fields_recursive_return_none() {
3160 let mut fields = HashSet::new();
3161 let stmts = vec![HirStmt::Return(None)];
3162 extract_fields_recursive(&stmts, "args", "command", &mut fields);
3163 assert!(fields.is_empty());
3164 }
3165
3166 #[test]
3167 fn test_extract_fields_recursive_if_no_else() {
3168 let mut fields = HashSet::new();
3169 let stmts = vec![HirStmt::If {
3170 condition: HirExpr::Literal(Literal::Bool(true)),
3171 then_body: vec![HirStmt::Expr(HirExpr::Attribute {
3172 value: Box::new(HirExpr::Var("args".to_string())),
3173 attr: "only_then".to_string(),
3174 })],
3175 else_body: None,
3176 }];
3177 extract_fields_recursive(&stmts, "args", "command", &mut fields);
3178 assert!(fields.contains("only_then"));
3179 }
3180
3181 #[test]
3182 fn test_extract_fields_recursive_multiple_stmts() {
3183 let mut fields = HashSet::new();
3184 let stmts = vec![
3185 HirStmt::Expr(HirExpr::Attribute {
3186 value: Box::new(HirExpr::Var("args".to_string())),
3187 attr: "first".to_string(),
3188 }),
3189 HirStmt::Expr(HirExpr::Attribute {
3190 value: Box::new(HirExpr::Var("args".to_string())),
3191 attr: "second".to_string(),
3192 }),
3193 ];
3194 extract_fields_recursive(&stmts, "args", "command", &mut fields);
3195 assert!(fields.contains("first"));
3196 assert!(fields.contains("second"));
3197 }
3198
3199 #[test]
3202 fn test_is_subcommand_check_non_binary() {
3203 let ctx = CodeGenContext::default();
3204 let expr = HirExpr::Var("something".to_string());
3205 let result = is_subcommand_check(&expr, "command", &ctx);
3206 assert_eq!(result, None);
3207 }
3208
3209 #[test]
3210 fn test_is_subcommand_check_non_attribute_left() {
3211 let ctx = CodeGenContext::default();
3212 let expr = HirExpr::Binary {
3213 op: BinOp::Eq,
3214 left: Box::new(HirExpr::Var("x".to_string())),
3215 right: Box::new(HirExpr::Literal(Literal::String("test".to_string()))),
3216 };
3217 let result = is_subcommand_check(&expr, "command", &ctx);
3218 assert_eq!(result, None);
3219 }
3220
3221 #[test]
3226 fn test_transpile_with_open_file() {
3227 let code = r#"
3228def read_file(path: str) -> str:
3229 with open(path) as f:
3230 return f.read()
3231"#;
3232 let rust = transpile(code);
3233 assert!(rust.contains("fn read_file"), "output: {}", rust);
3234 }
3235
3236 #[test]
3237 fn test_transpile_with_open_file_write() {
3238 let code = r#"
3239def write_file(path: str, data: str) -> None:
3240 with open(path, "w") as f:
3241 f.write(data)
3242"#;
3243 let rust = transpile(code);
3244 assert!(rust.contains("fn write_file"), "output: {}", rust);
3245 }
3246
3247 #[test]
3248 fn test_transpile_with_no_target() {
3249 let code = r#"
3250def process() -> None:
3251 with open("test.txt"):
3252 print("processing")
3253"#;
3254 let rust = transpile(code);
3255 assert!(rust.contains("fn process"), "output: {}", rust);
3256 }
3257
3258 #[test]
3263 fn test_transpile_raise_value_error() {
3264 let code = r#"
3265def validate(x: int) -> int:
3266 if x < 0:
3267 raise ValueError("must be positive")
3268 return x
3269"#;
3270 let rust = transpile(code);
3271 assert!(rust.contains("fn validate"), "output: {}", rust);
3272 }
3273
3274 #[test]
3275 fn test_transpile_raise_runtime_error() {
3276 let code = r#"
3277def fail_fast() -> None:
3278 raise RuntimeError("not implemented")
3279"#;
3280 let rust = transpile(code);
3281 assert!(rust.contains("fn fail_fast"), "output: {}", rust);
3282 }
3283
3284 #[test]
3285 fn test_transpile_raise_bare() {
3286 let code = r#"
3287def reraise_bare() -> int:
3288 try:
3289 return 1
3290 except ValueError:
3291 raise
3292"#;
3293 let rust = transpile(code);
3294 assert!(rust.contains("fn reraise_bare"), "output: {}", rust);
3295 }
3296
3297 #[test]
3302 fn test_transpile_assert_simple() {
3303 let code = r#"
3304def check(x: int) -> None:
3305 assert x > 0
3306"#;
3307 let rust = transpile(code);
3308 assert!(rust.contains("assert"), "output: {}", rust);
3309 }
3310
3311 #[test]
3312 fn test_transpile_assert_with_message() {
3313 let code = r#"
3314def check_msg(x: int) -> None:
3315 assert x > 0, "x must be positive"
3316"#;
3317 let rust = transpile(code);
3318 assert!(rust.contains("fn check_msg"), "output: {}", rust);
3319 assert!(
3320 rust.contains("assert") || rust.contains("panic"),
3321 "output: {}",
3322 rust
3323 );
3324 }
3325
3326 #[test]
3327 fn test_transpile_assert_equality() {
3328 let code = r#"
3329def check_eq(a: int, b: int) -> None:
3330 assert a == b
3331"#;
3332 let rust = transpile(code);
3333 assert!(rust.contains("fn check_eq"), "output: {}", rust);
3334 }
3335
3336 #[test]
3337 fn test_transpile_assert_not_equal() {
3338 let code = r#"
3339def check_ne(a: int, b: int) -> None:
3340 assert a != b
3341"#;
3342 let rust = transpile(code);
3343 assert!(rust.contains("fn check_ne"), "output: {}", rust);
3344 }
3345
3346 #[test]
3351 fn test_transpile_augmented_add() {
3352 let code = r#"
3353def accumulate(n: int) -> int:
3354 total = 0
3355 total += n
3356 return total
3357"#;
3358 let rust = transpile(code);
3359 assert!(rust.contains("fn accumulate"), "output: {}", rust);
3360 assert!(rust.contains("+="), "Should contain += operator: {}", rust);
3361 }
3362
3363 #[test]
3364 fn test_transpile_augmented_sub() {
3365 let code = r#"
3366def decrement(n: int) -> int:
3367 value = 10
3368 value -= n
3369 return value
3370"#;
3371 let rust = transpile(code);
3372 assert!(rust.contains("fn decrement"), "output: {}", rust);
3373 assert!(rust.contains("-="), "Should contain -= operator: {}", rust);
3374 }
3375
3376 #[test]
3377 fn test_transpile_augmented_mul() {
3378 let code = r#"
3379def scale(n: int) -> int:
3380 result = 1
3381 result *= n
3382 return result
3383"#;
3384 let rust = transpile(code);
3385 assert!(rust.contains("fn scale"), "output: {}", rust);
3386 assert!(
3388 rust.contains("*=") || rust.contains("py_mul") || rust.contains("* n"),
3389 "Should contain multiplication: {}",
3390 rust
3391 );
3392 }
3393
3394 #[test]
3395 fn test_transpile_augmented_mod() {
3396 let code = r#"
3397def modulo_assign(n: int) -> int:
3398 value = 100
3399 value %= n
3400 return value
3401"#;
3402 let rust = transpile(code);
3403 assert!(rust.contains("fn modulo_assign"), "output: {}", rust);
3404 assert!(
3406 rust.contains("%=") || rust.contains("py_mod") || rust.contains("mod"),
3407 "Should contain modulo operation: {}",
3408 rust
3409 );
3410 }
3411
3412 #[test]
3417 fn test_transpile_break_in_loop() {
3418 let code = r#"
3419def find_first(n: int) -> int:
3420 i = 0
3421 while i < n:
3422 if i == 5:
3423 break
3424 i += 1
3425 return i
3426"#;
3427 let rust = transpile(code);
3428 assert!(rust.contains("fn find_first"), "output: {}", rust);
3429 assert!(rust.contains("break"), "Should contain break: {}", rust);
3430 }
3431
3432 #[test]
3433 fn test_transpile_continue_in_loop() {
3434 let code = r#"
3435def skip_evens(n: int) -> int:
3436 total = 0
3437 for i in range(n):
3438 if i % 2 == 0:
3439 continue
3440 total += i
3441 return total
3442"#;
3443 let rust = transpile(code);
3444 assert!(rust.contains("fn skip_evens"), "output: {}", rust);
3445 assert!(
3446 rust.contains("continue"),
3447 "Should contain continue: {}",
3448 rust
3449 );
3450 }
3451
3452 #[test]
3457 fn test_transpile_pass_in_function() {
3458 let code = r#"
3459def noop() -> None:
3460 pass
3461"#;
3462 let rust = transpile(code);
3463 assert!(rust.contains("fn noop"), "output: {}", rust);
3464 }
3465
3466 #[test]
3467 fn test_transpile_pass_in_if() {
3468 let code = r#"
3469def conditional(x: int) -> int:
3470 if x > 0:
3471 pass
3472 else:
3473 return -1
3474 return x
3475"#;
3476 let rust = transpile(code);
3477 assert!(rust.contains("fn conditional"), "output: {}", rust);
3478 }
3479
3480 #[test]
3485 fn test_transpile_chained_assignment() {
3486 let code = r#"
3487def chain() -> int:
3488 a = b = 0
3489 return a + b
3490"#;
3491 let rust = transpile(code);
3492 assert!(rust.contains("fn chain"), "output: {}", rust);
3493 }
3494
3495 #[test]
3500 fn test_transpile_class_basic() {
3501 let code = r#"
3502class Point:
3503 def __init__(self, x: int, y: int):
3504 self.x = x
3505 self.y = y
3506"#;
3507 let rust = transpile(code);
3508 assert!(
3510 rust.contains("struct") || rust.contains("Point") || rust.contains("impl"),
3511 "Class should generate struct/impl: {}",
3512 rust
3513 );
3514 }
3515
3516 #[test]
3517 fn test_transpile_class_with_method() {
3518 let code = r#"
3519class Counter:
3520 def __init__(self):
3521 self.count = 0
3522
3523 def increment(self) -> None:
3524 self.count += 1
3525"#;
3526 let rust = transpile(code);
3527 assert!(
3528 rust.contains("Counter") || rust.contains("struct"),
3529 "Class with method should generate code: {}",
3530 rust
3531 );
3532 }
3533
3534 #[test]
3535 fn test_transpile_class_with_return_method() {
3536 let code = r#"
3537class Box:
3538 def __init__(self, value: int):
3539 self.value = value
3540
3541 def get_value(self) -> int:
3542 return self.value
3543"#;
3544 let rust = transpile(code);
3545 assert!(
3546 rust.contains("Box") || rust.contains("struct") || rust.contains("fn get_value"),
3547 "Class method should generate fn: {}",
3548 rust
3549 );
3550 }
3551
3552 #[test]
3557 fn test_transpile_try_except_with_exception_as_var() {
3558 let code = r#"
3559def handle_error(s: str) -> str:
3560 try:
3561 return s
3562 except Exception as e:
3563 return "error"
3564"#;
3565 let rust = transpile(code);
3566 assert!(rust.contains("fn handle_error"), "output: {}", rust);
3567 }
3568
3569 #[test]
3570 fn test_transpile_nested_try_with_finally() {
3571 let code = r#"
3572def nested_cleanup() -> str:
3573 result = ""
3574 try:
3575 try:
3576 result = "inner"
3577 except ValueError:
3578 result = "inner_error"
3579 finally:
3580 result = result + "_inner_done"
3581 except RuntimeError:
3582 result = "outer_error"
3583 return result
3584"#;
3585 let rust = transpile(code);
3586 assert!(rust.contains("fn nested_cleanup"), "output: {}", rust);
3587 }
3588
3589 #[test]
3590 fn test_transpile_try_only_finally() {
3591 let code = r#"
3592def with_finally() -> int:
3593 x = 0
3594 try:
3595 x = 42
3596 finally:
3597 x = x + 1
3598 return x
3599"#;
3600 let rust = transpile(code);
3601 assert!(rust.contains("fn with_finally"), "output: {}", rust);
3602 }
3603
3604 #[test]
3609 fn test_transpile_nested_function_recursive() {
3610 let code = r#"
3611def outer() -> int:
3612 def factorial(n: int) -> int:
3613 if n <= 1:
3614 return 1
3615 return n * factorial(n - 1)
3616 return factorial(5)
3617"#;
3618 let rust = transpile(code);
3619 assert!(rust.contains("fn outer"), "output: {}", rust);
3620 assert!(
3621 rust.contains("factorial"),
3622 "Should contain factorial: {}",
3623 rust
3624 );
3625 }
3626
3627 #[test]
3628 fn test_transpile_nested_function_unknown_return() {
3629 let code = r#"
3630def outer():
3631 def helper():
3632 return 42
3633 return helper()
3634"#;
3635 let rust = transpile(code);
3636 assert!(rust.contains("outer"), "output: {}", rust);
3637 assert!(rust.contains("helper"), "Should contain helper: {}", rust);
3638 }
3639
3640 #[test]
3645 fn test_transpile_string_concatenation_augmented() {
3646 let code = r#"
3647def build_str() -> str:
3648 result = ""
3649 result += "hello"
3650 result += " world"
3651 return result
3652"#;
3653 let rust = transpile(code);
3654 assert!(rust.contains("fn build_str"), "output: {}", rust);
3655 }
3656
3657 #[test]
3658 fn test_transpile_for_with_break() {
3659 let code = r#"
3660def search(items: list, target: int) -> int:
3661 for i in range(len(items)):
3662 if items[i] == target:
3663 return i
3664 return -1
3665"#;
3666 let rust = transpile(code);
3667 assert!(rust.contains("fn search"), "output: {}", rust);
3668 }
3669
3670 #[test]
3671 fn test_transpile_while_true_break() {
3672 let code = r#"
3673def read_until_done() -> int:
3674 count = 0
3675 while True:
3676 count += 1
3677 if count >= 10:
3678 break
3679 return count
3680"#;
3681 let rust = transpile(code);
3682 assert!(
3683 rust.contains("fn read_until_done"),
3684 "output: {}",
3685 rust
3686 );
3687 assert!(rust.contains("loop") || rust.contains("while"), "output: {}", rust);
3688 assert!(rust.contains("break"), "Should contain break: {}", rust);
3689 }
3690
3691 #[test]
3696 fn test_captures_outer_scope_in_with_context() {
3697 let params = vec![];
3698 let body = vec![HirStmt::With {
3699 context: HirExpr::Var("outer_ctx".to_string()),
3700 target: Some("f".to_string()),
3701 body: vec![],
3702 is_async: false,
3703 }];
3704 let outer_vars: HashSet<String> = ["outer_ctx".to_string()].into_iter().collect();
3705 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3706 }
3707
3708 #[test]
3709 fn test_captures_outer_scope_in_try_handler() {
3710 let params = vec![];
3711 let body = vec![HirStmt::Try {
3712 body: vec![],
3713 handlers: vec![ExceptHandler {
3714 exception_type: None,
3715 name: None,
3716 body: vec![HirStmt::Expr(HirExpr::Var("outer_val".to_string()))],
3717 }],
3718 orelse: None,
3719 finalbody: None,
3720 }];
3721 let outer_vars: HashSet<String> = ["outer_val".to_string()].into_iter().collect();
3722 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3723 }
3724
3725 #[test]
3726 fn test_captures_outer_scope_in_try_orelse() {
3727 let params = vec![];
3728 let body = vec![HirStmt::Try {
3729 body: vec![],
3730 handlers: vec![],
3731 orelse: Some(vec![HirStmt::Expr(HirExpr::Var(
3732 "else_var".to_string(),
3733 ))]),
3734 finalbody: None,
3735 }];
3736 let outer_vars: HashSet<String> = ["else_var".to_string()].into_iter().collect();
3737 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3738 }
3739
3740 #[test]
3741 fn test_captures_outer_scope_in_try_finalbody() {
3742 let params = vec![];
3743 let body = vec![HirStmt::Try {
3744 body: vec![],
3745 handlers: vec![],
3746 orelse: None,
3747 finalbody: Some(vec![HirStmt::Expr(HirExpr::Var(
3748 "fin_var".to_string(),
3749 ))]),
3750 }];
3751 let outer_vars: HashSet<String> = ["fin_var".to_string()].into_iter().collect();
3752 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3753 }
3754
3755 #[test]
3756 fn test_captures_outer_scope_in_method_call() {
3757 let params = vec![];
3758 let body = vec![HirStmt::Expr(HirExpr::MethodCall {
3759 object: Box::new(HirExpr::Var("outer_obj".to_string())),
3760 method: "do_thing".to_string(),
3761 args: vec![],
3762 kwargs: vec![],
3763 })];
3764 let outer_vars: HashSet<String> = ["outer_obj".to_string()].into_iter().collect();
3765 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3766 }
3767
3768 #[test]
3769 fn test_captures_outer_scope_in_attribute() {
3770 let params = vec![];
3771 let body = vec![HirStmt::Expr(HirExpr::Attribute {
3772 value: Box::new(HirExpr::Var("outer_ref".to_string())),
3773 attr: "field".to_string(),
3774 })];
3775 let outer_vars: HashSet<String> = ["outer_ref".to_string()].into_iter().collect();
3776 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3777 }
3778
3779 #[test]
3780 fn test_captures_outer_scope_in_index() {
3781 let params = vec![];
3782 let body = vec![HirStmt::Expr(HirExpr::Index {
3783 base: Box::new(HirExpr::Var("outer_list".to_string())),
3784 index: Box::new(HirExpr::Literal(Literal::Int(0))),
3785 })];
3786 let outer_vars: HashSet<String> = ["outer_list".to_string()].into_iter().collect();
3787 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3788 }
3789
3790 #[test]
3791 fn test_captures_outer_scope_in_if_expr() {
3792 let params = vec![];
3793 let body = vec![HirStmt::Expr(HirExpr::IfExpr {
3794 test: Box::new(HirExpr::Literal(Literal::Bool(true))),
3795 body: Box::new(HirExpr::Var("outer_val".to_string())),
3796 orelse: Box::new(HirExpr::Literal(Literal::Int(0))),
3797 })];
3798 let outer_vars: HashSet<String> = ["outer_val".to_string()].into_iter().collect();
3799 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3800 }
3801
3802 #[test]
3803 fn test_captures_outer_scope_in_unary() {
3804 let params = vec![];
3805 let body = vec![HirStmt::Expr(HirExpr::Unary {
3806 op: UnaryOp::Not,
3807 operand: Box::new(HirExpr::Var("flag".to_string())),
3808 })];
3809 let outer_vars: HashSet<String> = ["flag".to_string()].into_iter().collect();
3810 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3811 }
3812
3813 #[test]
3814 fn test_captures_outer_scope_in_list_literal() {
3815 let params = vec![];
3816 let body = vec![HirStmt::Expr(HirExpr::List(vec![HirExpr::Var(
3817 "outer_elem".to_string(),
3818 )]))];
3819 let outer_vars: HashSet<String> = ["outer_elem".to_string()].into_iter().collect();
3820 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3821 }
3822
3823 #[test]
3824 fn test_captures_outer_scope_in_dict_literal() {
3825 let params = vec![];
3826 let body = vec![HirStmt::Expr(HirExpr::Dict(vec![(
3827 HirExpr::Literal(Literal::String("k".to_string())),
3828 HirExpr::Var("outer_val".to_string()),
3829 )]))];
3830 let outer_vars: HashSet<String> = ["outer_val".to_string()].into_iter().collect();
3831 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3832 }
3833
3834 #[test]
3835 fn test_captures_outer_scope_in_assert() {
3836 let params = vec![];
3837 let body = vec![HirStmt::Assert {
3838 test: HirExpr::Var("invariant".to_string()),
3839 msg: None,
3840 }];
3841 let outer_vars: HashSet<String> = ["invariant".to_string()].into_iter().collect();
3842 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3843 }
3844
3845 #[test]
3846 fn test_captures_outer_scope_in_assert_msg() {
3847 let params = vec![];
3848 let body = vec![HirStmt::Assert {
3849 test: HirExpr::Literal(Literal::Bool(true)),
3850 msg: Some(HirExpr::Var("outer_msg".to_string())),
3851 }];
3852 let outer_vars: HashSet<String> = ["outer_msg".to_string()].into_iter().collect();
3853 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3854 }
3855
3856 #[test]
3857 fn test_captures_outer_scope_in_raise() {
3858 let params = vec![];
3859 let body = vec![HirStmt::Raise {
3860 exception: Some(HirExpr::Var("outer_exc".to_string())),
3861 cause: None,
3862 }];
3863 let outer_vars: HashSet<String> = ["outer_exc".to_string()].into_iter().collect();
3864 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3865 }
3866
3867 #[test]
3868 fn test_captures_outer_scope_in_raise_cause() {
3869 let params = vec![];
3870 let body = vec![HirStmt::Raise {
3871 exception: Some(HirExpr::Literal(Literal::String("err".to_string()))),
3872 cause: Some(HirExpr::Var("outer_cause".to_string())),
3873 }];
3874 let outer_vars: HashSet<String> = ["outer_cause".to_string()].into_iter().collect();
3875 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3876 }
3877
3878 #[test]
3879 fn test_captures_outer_scope_nested_function_def() {
3880 let params = vec![];
3881 let body = vec![HirStmt::FunctionDef {
3882 name: "nested".to_string(),
3883 params: Box::new(smallvec::smallvec![]),
3884 ret_type: Type::Int,
3885 body: vec![HirStmt::Return(Some(HirExpr::Var(
3886 "outer_val".to_string(),
3887 )))],
3888 docstring: None,
3889 }];
3890 let outer_vars: HashSet<String> = ["outer_val".to_string()].into_iter().collect();
3891 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3892 }
3893
3894 #[test]
3895 fn test_captures_outer_scope_in_block() {
3896 let params = vec![];
3897 let body = vec![HirStmt::Block(vec![HirStmt::Expr(HirExpr::Var(
3898 "outer_block".to_string(),
3899 ))])];
3900 let outer_vars: HashSet<String> = ["outer_block".to_string()].into_iter().collect();
3901 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3902 }
3903
3904 #[test]
3905 fn test_captures_outer_scope_call_outer_func() {
3906 let params = vec![];
3907 let body = vec![HirStmt::Expr(HirExpr::Call {
3908 func: "outer_func".to_string(),
3909 args: vec![],
3910 kwargs: vec![],
3911 })];
3912 let outer_vars: HashSet<String> = ["outer_func".to_string()].into_iter().collect();
3913 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3914 }
3915
3916 #[test]
3917 fn test_captures_outer_scope_call_kwarg() {
3918 let params = vec![];
3919 let body = vec![HirStmt::Expr(HirExpr::Call {
3920 func: "print".to_string(),
3921 args: vec![],
3922 kwargs: vec![("end".to_string(), HirExpr::Var("outer_kw".to_string()))],
3923 })];
3924 let outer_vars: HashSet<String> = ["outer_kw".to_string()].into_iter().collect();
3925 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
3926 }
3927
3928 #[test]
3929 fn test_captures_outer_scope_no_capture_pass() {
3930 let params = vec![];
3931 let body = vec![HirStmt::Pass];
3932 let outer_vars: HashSet<String> = ["anything".to_string()].into_iter().collect();
3933 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
3934 }
3935
3936 #[test]
3937 fn test_captures_outer_scope_no_capture_return_none() {
3938 let params = vec![];
3939 let body = vec![HirStmt::Return(None)];
3940 let outer_vars: HashSet<String> = ["anything".to_string()].into_iter().collect();
3941 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
3942 }
3943
3944 #[test]
3945 fn test_captures_outer_scope_no_capture_break() {
3946 let params = vec![];
3947 let body = vec![HirStmt::Break { label: None }];
3948 let outer_vars: HashSet<String> = ["anything".to_string()].into_iter().collect();
3949 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
3950 }
3951
3952 #[test]
3953 fn test_captures_outer_scope_no_capture_continue() {
3954 let params = vec![];
3955 let body = vec![HirStmt::Continue { label: None }];
3956 let outer_vars: HashSet<String> = ["anything".to_string()].into_iter().collect();
3957 assert!(!captures_outer_scope(¶ms, &body, &outer_vars));
3958 }
3959
3960 #[test]
3965 fn test_transpile_try_parse_int_with_negative_fallback() {
3966 let code = r#"
3967def safe_parse(s: str) -> int:
3968 try:
3969 return int(s)
3970 except ValueError:
3971 return -1
3972"#;
3973 let rust = transpile(code);
3974 assert!(rust.contains("fn safe_parse"), "output: {}", rust);
3975 assert!(
3976 rust.contains("parse") || rust.contains("unwrap_or"),
3977 "Should use parse pattern: {}",
3978 rust
3979 );
3980 }
3981
3982 #[test]
3983 fn test_transpile_try_parse_int_with_zero_fallback() {
3984 let code = r#"
3985def parse_or_zero(s: str) -> int:
3986 try:
3987 return int(s)
3988 except ValueError:
3989 return 0
3990"#;
3991 let rust = transpile(code);
3992 assert!(rust.contains("fn parse_or_zero"), "output: {}", rust);
3993 }
3994
3995 #[test]
3996 fn test_transpile_try_handler_with_print() {
3997 let code = r#"
3998def safe_op() -> str:
3999 try:
4000 return "ok"
4001 except ValueError:
4002 print("error occurred")
4003 return "error"
4004"#;
4005 let rust = transpile(code);
4006 assert!(rust.contains("fn safe_op"), "output: {}", rust);
4007 }
4008
4009 #[test]
4014 fn test_extract_fields_multiple_in_same_call() {
4015 let mut fields = HashSet::new();
4016 let expr = HirExpr::Call {
4017 func: "combine".to_string(),
4018 args: vec![
4019 HirExpr::Attribute {
4020 value: Box::new(HirExpr::Var("args".to_string())),
4021 attr: "first".to_string(),
4022 },
4023 HirExpr::Attribute {
4024 value: Box::new(HirExpr::Var("args".to_string())),
4025 attr: "second".to_string(),
4026 },
4027 ],
4028 kwargs: vec![],
4029 };
4030 extract_fields_from_expr(&expr, "args", "command", &mut fields);
4031 assert!(fields.contains("first"));
4032 assert!(fields.contains("second"));
4033 assert_eq!(fields.len(), 2);
4034 }
4035
4036 #[test]
4037 fn test_extract_fields_from_dict_key() {
4038 let mut fields = HashSet::new();
4039 let expr = HirExpr::Dict(vec![(
4040 HirExpr::Attribute {
4041 value: Box::new(HirExpr::Var("args".to_string())),
4042 attr: "key_field".to_string(),
4043 },
4044 HirExpr::Literal(Literal::Int(42)),
4045 )]);
4046 extract_fields_from_expr(&expr, "args", "command", &mut fields);
4047 assert!(fields.contains("key_field"));
4048 }
4049
4050 #[test]
4051 fn test_extract_fields_var_no_match() {
4052 let mut fields = HashSet::new();
4053 let expr = HirExpr::Var("something".to_string());
4054 extract_fields_from_expr(&expr, "args", "command", &mut fields);
4055 assert!(fields.is_empty());
4056 }
4057
4058 #[test]
4063 fn test_extract_parse_from_tokens_empty() {
4064 let result = extract_parse_from_tokens(&[]);
4065 assert!(result.is_none());
4066 }
4067
4068 #[test]
4069 fn test_extract_parse_from_tokens_non_parse_stmt() {
4070 let tokens: proc_macro2::TokenStream =
4071 "let x = 42 ;".parse().unwrap();
4072 let result = extract_parse_from_tokens(&[tokens]);
4073 assert!(result.is_none());
4074 }
4075
4076 #[test]
4081 fn test_transpile_try_except_with_variable_hoisting() {
4082 let code = r#"
4083def process(s: str) -> str:
4084 try:
4085 result = s.upper()
4086 except ValueError:
4087 result = "error"
4088 return result
4089"#;
4090 let rust = transpile(code);
4091 assert!(rust.contains("fn process"), "output: {}", rust);
4092 assert!(
4093 rust.contains("result") && rust.contains("mut"),
4094 "Should hoist result variable: {}",
4095 rust
4096 );
4097 }
4098
4099 #[test]
4100 fn test_transpile_try_with_floor_div_zerodiv() {
4101 let code = r#"
4102def safe_floor_div(a: int, b: int) -> int:
4103 try:
4104 return a // b
4105 except ZeroDivisionError:
4106 return 0
4107"#;
4108 let rust = transpile(code);
4109 assert!(rust.contains("fn safe_floor_div"), "output: {}", rust);
4110 }
4111
4112 #[test]
4113 fn test_transpile_try_exception_with_binding_and_return() {
4114 let code = r#"
4115def catch_with_msg(s: str) -> str:
4116 try:
4117 n = int(s)
4118 return str(n)
4119 except ValueError as e:
4120 return "bad input"
4121"#;
4122 let rust = transpile(code);
4123 assert!(rust.contains("fn catch_with_msg"), "output: {}", rust);
4124 }
4125
4126 #[test]
4127 fn test_transpile_try_except_in_loop() {
4128 let code = r#"
4129def parse_all(items: list) -> int:
4130 count = 0
4131 for item in items:
4132 try:
4133 n = int(item)
4134 count += n
4135 except ValueError:
4136 pass
4137 return count
4138"#;
4139 let rust = transpile(code);
4140 assert!(rust.contains("fn parse_all"), "output: {}", rust);
4141 }
4142
4143 #[test]
4144 fn test_transpile_try_with_bool_return_handler() {
4145 let code = r#"
4146def is_valid(s: str) -> bool:
4147 try:
4148 n = int(s)
4149 return True
4150 except ValueError:
4151 return False
4152"#;
4153 let rust = transpile(code);
4154 assert!(rust.contains("fn is_valid"), "output: {}", rust);
4155 }
4156
4157 #[test]
4158 fn test_transpile_try_negative_fallback() {
4159 let code = r#"
4160def parse_or_neg(s: str) -> int:
4161 try:
4162 return int(s)
4163 except ValueError:
4164 return -1
4165"#;
4166 let rust = transpile(code);
4167 assert!(rust.contains("fn parse_or_neg"), "output: {}", rust);
4168 }
4169
4170 #[test]
4171 fn test_transpile_try_float_fallback() {
4172 let code = r#"
4173def parse_float_safe(s: str) -> float:
4174 try:
4175 return float(s)
4176 except ValueError:
4177 return 0.0
4178"#;
4179 let rust = transpile(code);
4180 assert!(rust.contains("fn parse_float_safe"), "output: {}", rust);
4181 }
4182
4183 #[test]
4184 fn test_transpile_try_string_fallback() {
4185 let code = r#"
4186def safe_upper(s: str) -> str:
4187 try:
4188 return s.upper()
4189 except ValueError:
4190 return ""
4191"#;
4192 let rust = transpile(code);
4193 assert!(rust.contains("fn safe_upper"), "output: {}", rust);
4194 }
4195
4196 #[test]
4197 fn test_transpile_nested_function_with_capture_list() {
4198 let code = r#"
4199def outer() -> list:
4200 items = [1, 2, 3]
4201 def inner() -> int:
4202 return len(items)
4203 return [inner()]
4204"#;
4205 let rust = transpile(code);
4206 assert!(rust.contains("fn outer"), "output: {}", rust);
4207 }
4208
4209 #[test]
4210 fn test_transpile_nested_function_with_string_capture() {
4211 let code = r#"
4212def make_greeter(name: str) -> str:
4213 def greet() -> str:
4214 return "Hello " + name
4215 return greet()
4216"#;
4217 let rust = transpile(code);
4218 assert!(rust.contains("fn make_greeter"), "output: {}", rust);
4219 }
4220
4221 #[test]
4222 fn test_transpile_nested_function_no_params_no_capture() {
4223 let code = r#"
4224def wrapper() -> int:
4225 def constant() -> int:
4226 return 42
4227 return constant()
4228"#;
4229 let rust = transpile(code);
4230 assert!(rust.contains("fn wrapper"), "output: {}", rust);
4231 }
4232
4233 #[test]
4234 fn test_transpile_nested_function_shadowed_param() {
4235 let code = r#"
4236def outer(x: int) -> int:
4237 y = 20
4238 def inner(x: int) -> int:
4239 return x + y
4240 return inner(5)
4241"#;
4242 let rust = transpile(code);
4243 assert!(rust.contains("fn outer"), "output: {}", rust);
4244 }
4245
4246 #[test]
4247 fn test_transpile_try_except_os_error() {
4248 let code = r#"
4249def safe_read(path: str) -> str:
4250 try:
4251 with open(path) as f:
4252 return f.read()
4253 except OSError:
4254 return ""
4255"#;
4256 let rust = transpile(code);
4257 assert!(rust.contains("fn safe_read"), "output: {}", rust);
4258 }
4259
4260 #[test]
4261 fn test_transpile_try_except_index_error() {
4262 let code = r#"
4263def safe_first(items: list) -> int:
4264 try:
4265 return items[0]
4266 except IndexError:
4267 return 0
4268"#;
4269 let rust = transpile(code);
4270 assert!(rust.contains("fn safe_first"), "output: {}", rust);
4271 }
4272
4273 #[test]
4274 fn test_transpile_try_except_key_error() {
4275 let code = r#"
4276def safe_lookup(d: dict, key: str) -> str:
4277 try:
4278 return d[key]
4279 except KeyError:
4280 return "missing"
4281"#;
4282 let rust = transpile(code);
4283 assert!(rust.contains("fn safe_lookup"), "output: {}", rust);
4284 }
4285
4286 #[test]
4287 fn test_transpile_try_except_with_print_handler() {
4288 let code = r#"
4289def log_error(s: str) -> int:
4290 try:
4291 return int(s)
4292 except ValueError:
4293 print("invalid")
4294 return 0
4295"#;
4296 let rust = transpile(code);
4297 assert!(rust.contains("fn log_error"), "output: {}", rust);
4298 }
4299
4300 #[test]
4301 fn test_transpile_try_except_multiple_stmts_body() {
4302 let code = r#"
4303def multi_try(a: str, b: str) -> int:
4304 try:
4305 x = int(a)
4306 y = int(b)
4307 return x + y
4308 except ValueError:
4309 return 0
4310"#;
4311 let rust = transpile(code);
4312 assert!(rust.contains("fn multi_try"), "output: {}", rust);
4313 }
4314
4315 #[test]
4318 fn test_captures_outer_scope_in_assign_value() {
4319 let params = vec![];
4320 let body = vec![HirStmt::Assign {
4321 target: AssignTarget::Symbol("x".to_string()),
4322 value: HirExpr::Binary {
4323 op: BinOp::Add,
4324 left: Box::new(HirExpr::Var("outer_val".to_string())),
4325 right: Box::new(HirExpr::Literal(Literal::Int(1))),
4326 },
4327 type_annotation: None,
4328 }];
4329 let outer_vars: HashSet<String> = ["outer_val".to_string()].into_iter().collect();
4330 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4331 }
4332
4333 #[test]
4334 fn test_captures_outer_scope_in_call_args() {
4335 let params = vec![];
4336 let body = vec![HirStmt::Expr(HirExpr::Call {
4337 func: "print".to_string(),
4338 args: vec![HirExpr::Var("captured".to_string())],
4339 kwargs: vec![],
4340 })];
4341 let outer_vars: HashSet<String> = ["captured".to_string()].into_iter().collect();
4342 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4343 }
4344
4345 #[test]
4346 fn test_captures_outer_scope_in_method_call_object() {
4347 let params = vec![];
4348 let body = vec![HirStmt::Expr(HirExpr::MethodCall {
4349 object: Box::new(HirExpr::Var("outer_list".to_string())),
4350 method: "append".to_string(),
4351 args: vec![HirExpr::Literal(Literal::Int(1))],
4352 kwargs: vec![],
4353 })];
4354 let outer_vars: HashSet<String> = ["outer_list".to_string()].into_iter().collect();
4355 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4356 }
4357
4358 #[test]
4359 fn test_captures_outer_scope_in_comprehension() {
4360 let params = vec![];
4361 let body = vec![HirStmt::Return(Some(HirExpr::ListComp {
4362 element: Box::new(HirExpr::Binary {
4363 op: BinOp::Mul,
4364 left: Box::new(HirExpr::Var("x".to_string())),
4365 right: Box::new(HirExpr::Var("multiplier".to_string())),
4366 }),
4367 generators: vec![HirComprehension {
4368 target: "x".to_string(),
4369 iter: Box::new(HirExpr::List(vec![])),
4370 conditions: vec![],
4371 }],
4372 }))];
4373 let outer_vars: HashSet<String> = ["multiplier".to_string()].into_iter().collect();
4374 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4375 }
4376
4377 #[test]
4378 fn test_captures_outer_scope_in_fstring() {
4379 let params = vec![];
4380 let body = vec![HirStmt::Return(Some(HirExpr::FString {
4381 parts: vec![FStringPart::Expr(Box::new(HirExpr::Var(
4382 "name".to_string(),
4383 )))],
4384 }))];
4385 let outer_vars: HashSet<String> = ["name".to_string()].into_iter().collect();
4386 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4387 }
4388
4389 #[test]
4390 fn test_captures_outer_scope_s9_index() {
4391 let params = vec![];
4392 let body = vec![HirStmt::Return(Some(HirExpr::Index {
4393 base: Box::new(HirExpr::Var("data".to_string())),
4394 index: Box::new(HirExpr::Literal(Literal::Int(0))),
4395 }))];
4396 let outer_vars: HashSet<String> = ["data".to_string()].into_iter().collect();
4397 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4398 }
4399
4400 #[test]
4401 fn test_captures_outer_scope_s9_attribute() {
4402 let params = vec![];
4403 let body = vec![HirStmt::Return(Some(HirExpr::Attribute {
4404 value: Box::new(HirExpr::Var("obj".to_string())),
4405 attr: "name".to_string(),
4406 }))];
4407 let outer_vars: HashSet<String> = ["obj".to_string()].into_iter().collect();
4408 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4409 }
4410
4411 #[test]
4412 fn test_captures_outer_scope_s9_if_expr() {
4413 let params = vec![];
4414 let body = vec![HirStmt::Return(Some(HirExpr::IfExpr {
4415 test: Box::new(HirExpr::Var("flag".to_string())),
4416 body: Box::new(HirExpr::Literal(Literal::Int(1))),
4417 orelse: Box::new(HirExpr::Literal(Literal::Int(0))),
4418 }))];
4419 let outer_vars: HashSet<String> = ["flag".to_string()].into_iter().collect();
4420 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4421 }
4422
4423 #[test]
4424 fn test_captures_outer_scope_in_try_body() {
4425 let params = vec![];
4426 let body = vec![HirStmt::Try {
4427 body: vec![HirStmt::Expr(HirExpr::Var("resource".to_string()))],
4428 handlers: vec![],
4429 orelse: None,
4430 finalbody: None,
4431 }];
4432 let outer_vars: HashSet<String> = ["resource".to_string()].into_iter().collect();
4433 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4434 }
4435
4436 #[test]
4437 fn test_captures_outer_scope_s9_with_context() {
4438 let params = vec![];
4439 let body = vec![HirStmt::With {
4440 context: HirExpr::Var("manager".to_string()),
4441 target: Some("m".to_string()),
4442 body: vec![],
4443 is_async: false,
4444 }];
4445 let outer_vars: HashSet<String> = ["manager".to_string()].into_iter().collect();
4446 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4447 }
4448
4449 #[test]
4450 fn test_captures_outer_scope_in_dict_comp() {
4451 let params = vec![];
4452 let body = vec![HirStmt::Return(Some(HirExpr::DictComp {
4453 key: Box::new(HirExpr::Var("k".to_string())),
4454 value: Box::new(HirExpr::Var("default_val".to_string())),
4455 generators: vec![HirComprehension {
4456 target: "k".to_string(),
4457 iter: Box::new(HirExpr::List(vec![])),
4458 conditions: vec![],
4459 }],
4460 }))];
4461 let outer_vars: HashSet<String> = ["default_val".to_string()].into_iter().collect();
4462 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4463 }
4464
4465 #[test]
4466 fn test_captures_outer_scope_in_set_comp() {
4467 let params = vec![];
4468 let body = vec![HirStmt::Return(Some(HirExpr::SetComp {
4469 element: Box::new(HirExpr::Var("transform".to_string())),
4470 generators: vec![HirComprehension {
4471 target: "x".to_string(),
4472 iter: Box::new(HirExpr::List(vec![])),
4473 conditions: vec![],
4474 }],
4475 }))];
4476 let outer_vars: HashSet<String> = ["transform".to_string()].into_iter().collect();
4477 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4478 }
4479
4480 #[test]
4481 fn test_captures_outer_scope_in_generator_exp() {
4482 let params = vec![];
4483 let body = vec![HirStmt::Return(Some(HirExpr::GeneratorExp {
4484 element: Box::new(HirExpr::Binary {
4485 op: BinOp::Add,
4486 left: Box::new(HirExpr::Var("x".to_string())),
4487 right: Box::new(HirExpr::Var("offset".to_string())),
4488 }),
4489 generators: vec![HirComprehension {
4490 target: "x".to_string(),
4491 iter: Box::new(HirExpr::List(vec![])),
4492 conditions: vec![],
4493 }],
4494 }))];
4495 let outer_vars: HashSet<String> = ["offset".to_string()].into_iter().collect();
4496 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4497 }
4498
4499 #[test]
4500 fn test_captures_outer_scope_in_compare() {
4501 let params = vec![];
4502 let body = vec![HirStmt::Return(Some(HirExpr::Binary {
4503 op: BinOp::Gt,
4504 left: Box::new(HirExpr::Var("threshold".to_string())),
4505 right: Box::new(HirExpr::Literal(Literal::Int(0))),
4506 }))];
4507 let outer_vars: HashSet<String> = ["threshold".to_string()].into_iter().collect();
4508 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4509 }
4510
4511 #[test]
4512 fn test_captures_outer_scope_in_lambda() {
4513 let params = vec![];
4514 let body = vec![HirStmt::Return(Some(HirExpr::Lambda {
4515 params: vec![],
4516 body: Box::new(HirExpr::Var("captured".to_string())),
4517 }))];
4518 let outer_vars: HashSet<String> = ["captured".to_string()].into_iter().collect();
4519 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4520 }
4521
4522 #[test]
4523 fn test_captures_outer_scope_yield() {
4524 let params = vec![];
4525 let body = vec![HirStmt::Expr(HirExpr::Yield {
4526 value: Some(Box::new(HirExpr::Var("yielded".to_string()))),
4527 })];
4528 let outer_vars: HashSet<String> = ["yielded".to_string()].into_iter().collect();
4529 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4530 }
4531
4532 #[test]
4533 fn test_captures_outer_scope_named_expr() {
4534 let params = vec![];
4535 let body = vec![HirStmt::Return(Some(HirExpr::NamedExpr {
4536 target: "x".to_string(),
4537 value: Box::new(HirExpr::Var("source".to_string())),
4538 }))];
4539 let outer_vars: HashSet<String> = ["source".to_string()].into_iter().collect();
4540 assert!(captures_outer_scope(¶ms, &body, &outer_vars));
4541 }
4542
4543 #[test]
4546 fn test_transpile_try_with_variable_escape() {
4547 let code = r#"
4548def escape_var(s: str) -> str:
4549 try:
4550 result = s.strip()
4551 n = int(result)
4552 except ValueError:
4553 n = 0
4554 return str(n)
4555"#;
4556 let rust = transpile(code);
4557 assert!(rust.contains("fn escape_var"), "output: {}", rust);
4558 }
4559
4560 #[test]
4561 fn test_transpile_nested_fn_recursive() {
4562 let code = r#"
4563def counter() -> int:
4564 count = 0
4565 def increment() -> int:
4566 return count + 1
4567 return increment()
4568"#;
4569 let rust = transpile(code);
4570 assert!(rust.contains("fn counter"), "output: {}", rust);
4571 }
4572
4573 #[test]
4574 fn test_transpile_try_with_type_error() {
4575 let code = r#"
4576def type_safe(x: int) -> str:
4577 try:
4578 return str(x)
4579 except TypeError:
4580 return "type error"
4581"#;
4582 let rust = transpile(code);
4583 assert!(rust.contains("fn type_safe"), "output: {}", rust);
4584 }
4585
4586 #[test]
4587 fn test_transpile_try_with_attribute_error() {
4588 let code = r#"
4589def attr_safe(s: str) -> str:
4590 try:
4591 return s.upper()
4592 except AttributeError:
4593 return ""
4594"#;
4595 let rust = transpile(code);
4596 assert!(rust.contains("fn attr_safe"), "output: {}", rust);
4597 }
4598
4599 #[test]
4600 fn test_transpile_try_all_except_types() {
4601 let code = r#"
4602def robust(x: int) -> int:
4603 try:
4604 return x + 1
4605 except (ValueError, TypeError, IndexError):
4606 return 0
4607"#;
4608 let rust = transpile(code);
4609 assert!(rust.contains("fn robust"), "output: {}", rust);
4610 }
4611
4612 #[test]
4613 fn test_transpile_del_statement() {
4614 let code = r#"
4615def cleanup() -> None:
4616 x = 10
4617 del x
4618"#;
4619 let rust = transpile(code);
4620 assert!(rust.contains("fn cleanup"), "output: {}", rust);
4621 }
4622
4623 #[test]
4624 fn test_transpile_global_statement() {
4625 let code = r#"
4626counter = 0
4627
4628def increment() -> int:
4629 global counter
4630 counter += 1
4631 return counter
4632"#;
4633 let rust = transpile(code);
4634 assert!(
4635 rust.contains("fn increment") || rust.contains("counter"),
4636 "output: {}",
4637 rust
4638 );
4639 }
4640
4641 #[test]
4642 fn test_transpile_nested_fn_with_default_param() {
4643 let code = r#"
4644def outer() -> int:
4645 base = 10
4646 def inner(x: int = 5) -> int:
4647 return x + base
4648 return inner()
4649"#;
4650 let rust = transpile(code);
4651 assert!(rust.contains("fn outer"), "output: {}", rust);
4652 }
4653
4654 #[test]
4655 fn test_transpile_try_except_with_list_ops_in_handler() {
4656 let code = r#"
4657def collect_errors() -> list:
4658 errors = []
4659 try:
4660 x = int("bad")
4661 except ValueError:
4662 errors.append("parse error")
4663 return errors
4664"#;
4665 let rust = transpile(code);
4666 assert!(rust.contains("fn collect_errors"), "output: {}", rust);
4667 }
4668
4669 #[test]
4672 fn test_s9_try_except_bool_return_with_finally() {
4673 let code = r#"
4674def check_valid(s: str) -> bool:
4675 try:
4676 x = int(s)
4677 return True
4678 except ValueError:
4679 return False
4680 finally:
4681 print("done")
4682"#;
4683 let rust = transpile(code);
4684 assert!(rust.contains("fn check_valid"), "output: {}", rust);
4685 }
4686
4687 #[test]
4688 fn test_s9_try_except_with_string_return_fallback() {
4689 let code = r#"
4690def safe_parse(s: str) -> str:
4691 try:
4692 return str(int(s))
4693 except ValueError:
4694 return "invalid"
4695"#;
4696 let rust = transpile(code);
4697 assert!(rust.contains("fn safe_parse"), "output: {}", rust);
4698 }
4699
4700 #[test]
4701 fn test_s9_try_except_with_float_fallback() {
4702 let code = r#"
4703def to_float(s: str) -> float:
4704 try:
4705 return float(s)
4706 except ValueError:
4707 return 0.0
4708"#;
4709 let rust = transpile(code);
4710 assert!(rust.contains("fn to_float"), "output: {}", rust);
4711 }
4712
4713 #[test]
4714 fn test_s9_try_except_with_negative_int_fallback() {
4715 let code = r#"
4716def parse_or_neg(s: str) -> int:
4717 try:
4718 return int(s)
4719 except ValueError:
4720 return -1
4721"#;
4722 let rust = transpile(code);
4723 assert!(rust.contains("fn parse_or_neg"), "output: {}", rust);
4724 }
4725
4726 #[test]
4727 fn test_s9_try_except_with_exception_binding() {
4728 let code = r#"
4729def handle_error(s: str) -> str:
4730 try:
4731 x = int(s)
4732 return str(x)
4733 except ValueError as e:
4734 return str(e)
4735"#;
4736 let rust = transpile(code);
4737 assert!(rust.contains("fn handle_error"), "output: {}", rust);
4738 }
4739
4740 #[test]
4741 fn test_s9_try_except_assignment_in_body_and_handler() {
4742 let code = r#"
4743def dual_assign(s: str) -> int:
4744 result = 0
4745 try:
4746 result = int(s)
4747 except ValueError:
4748 result = -1
4749 return result
4750"#;
4751 let rust = transpile(code);
4752 assert!(rust.contains("fn dual_assign"), "output: {}", rust);
4753 }
4754
4755 #[test]
4756 fn test_s9_try_except_with_loop_in_body() {
4757 let code = r#"
4758def sum_strings(items: list) -> int:
4759 total = 0
4760 try:
4761 for item in items:
4762 total += int(item)
4763 except ValueError:
4764 total = -1
4765 return total
4766"#;
4767 let rust = transpile(code);
4768 assert!(rust.contains("fn sum_strings"), "output: {}", rust);
4769 }
4770
4771 #[test]
4772 fn test_s9_nested_function_recursive() {
4773 let code = r#"
4774def outer(n: int) -> int:
4775 def factorial(x: int) -> int:
4776 if x <= 1:
4777 return 1
4778 return x * factorial(x - 1)
4779 return factorial(n)
4780"#;
4781 let rust = transpile(code);
4782 assert!(rust.contains("fn outer"), "output: {}", rust);
4783 }
4784
4785 #[test]
4786 fn test_s9_nested_function_with_string_param() {
4787 let code = r#"
4788def process(text: str) -> str:
4789 def transform(s: str) -> str:
4790 return s.upper()
4791 return transform(text)
4792"#;
4793 let rust = transpile(code);
4794 assert!(rust.contains("fn process"), "output: {}", rust);
4795 }
4796
4797 #[test]
4798 fn test_s9_nested_function_with_list_param() {
4799 let code = r#"
4800def filter_positive(nums: list) -> list:
4801 def is_positive(n: int) -> bool:
4802 return n > 0
4803 result = []
4804 for n in nums:
4805 if is_positive(n):
4806 result.append(n)
4807 return result
4808"#;
4809 let rust = transpile(code);
4810 assert!(rust.contains("fn filter_positive"), "output: {}", rust);
4811 }
4812
4813 #[test]
4814 fn test_s9_nested_function_with_dict_param() {
4815 let code = r#"
4816def get_value(data: dict, key: str) -> int:
4817 def lookup(d: dict, k: str) -> int:
4818 return d.get(k, 0)
4819 return lookup(data, key)
4820"#;
4821 let rust = transpile(code);
4822 assert!(rust.contains("fn get_value"), "output: {}", rust);
4823 }
4824
4825 #[test]
4826 fn test_s9_nested_function_void_return() {
4827 let code = r#"
4828def run() -> None:
4829 def helper() -> None:
4830 print("help")
4831 helper()
4832"#;
4833 let rust = transpile(code);
4834 assert!(rust.contains("fn run"), "output: {}", rust);
4835 }
4836
4837 #[test]
4838 fn test_s9_try_except_multiple_handlers_with_binding() {
4839 let code = r#"
4840def multi_catch(s: str) -> str:
4841 try:
4842 return str(int(s))
4843 except ValueError as e:
4844 return "value error"
4845 except TypeError as e:
4846 return "type error"
4847"#;
4848 let rust = transpile(code);
4849 assert!(rust.contains("fn multi_catch"), "output: {}", rust);
4850 }
4851
4852 #[test]
4853 fn test_s9_try_except_floor_div_zero_division() {
4854 let code = r#"
4855def safe_floor_div(a: int, b: int) -> int:
4856 try:
4857 return a // b
4858 except ZeroDivisionError:
4859 return 0
4860"#;
4861 let rust = transpile(code);
4862 assert!(rust.contains("fn safe_floor_div"), "output: {}", rust);
4863 }
4864
4865 #[test]
4866 fn test_s9_nested_try_except() {
4867 let code = r#"
4868def nested_error_handling(s: str) -> int:
4869 try:
4870 try:
4871 return int(s)
4872 except ValueError:
4873 return -1
4874 except TypeError:
4875 return -2
4876"#;
4877 let rust = transpile(code);
4878 assert!(rust.contains("fn nested_error_handling"), "output: {}", rust);
4879 }
4880
4881 #[test]
4882 fn test_s9_try_finally_no_except() {
4883 let code = r#"
4884def always_cleanup() -> int:
4885 x = 0
4886 try:
4887 x = 42
4888 finally:
4889 print("cleanup")
4890 return x
4891"#;
4892 let rust = transpile(code);
4893 assert!(rust.contains("fn always_cleanup"), "output: {}", rust);
4894 }
4895
4896 #[test]
4897 fn test_s9_nested_function_with_return_type_list() {
4898 let code = r#"
4899def make_list() -> list:
4900 def create() -> list:
4901 result = []
4902 result.append(1)
4903 result.append(2)
4904 return result
4905 return create()
4906"#;
4907 let rust = transpile(code);
4908 assert!(rust.contains("fn make_list"), "output: {}", rust);
4909 }
4910
4911 #[test]
4914 fn test_s9b6_parse_from_tokens_expr_stmt() {
4915 let code = r#"
4916def parse_int(s: str) -> int:
4917 x = int("42")
4918 return x
4919"#;
4920 let rust = transpile(code);
4921 assert!(rust.contains("fn parse_int"), "output: {}", rust);
4922 }
4923
4924 #[test]
4925 fn test_s9b6_parse_from_tokens_in_try() {
4926 let code = r#"
4927def safe_parse(s: str) -> int:
4928 try:
4929 x = int(s)
4930 return x
4931 except ValueError:
4932 return 0
4933"#;
4934 let rust = transpile(code);
4935 assert!(rust.contains("fn safe_parse"), "output: {}", rust);
4936 }
4937
4938 #[test]
4939 fn test_s9b6_try_with_else_block() {
4940 let code = r#"
4941def try_else(s: str) -> str:
4942 try:
4943 x = int(s)
4944 except ValueError:
4945 return "error"
4946 else:
4947 return "success"
4948"#;
4949 let rust = transpile(code);
4950 assert!(rust.contains("fn try_else"), "output: {}", rust);
4951 }
4952
4953 #[test]
4954 fn test_s9b6_bare_except() {
4955 let code = r#"
4956def catch_all(s: str) -> int:
4957 try:
4958 return int(s)
4959 except:
4960 return -1
4961"#;
4962 let rust = transpile(code);
4963 assert!(rust.contains("fn catch_all"), "output: {}", rust);
4964 }
4965
4966 #[test]
4967 fn test_s9b6_multiple_exception_types() {
4968 let code = r#"
4969def multi_handler(s: str) -> int:
4970 try:
4971 return int(s)
4972 except ValueError:
4973 return -1
4974 except TypeError:
4975 return -2
4976 except KeyError:
4977 return -3
4978"#;
4979 let rust = transpile(code);
4980 assert!(rust.contains("fn multi_handler"), "output: {}", rust);
4981 }
4982
4983 #[test]
4984 fn test_s9b6_string_ops_in_try() {
4985 let code = r#"
4986def string_transform(s: str) -> str:
4987 try:
4988 result = s.upper()
4989 result = result.strip()
4990 return result
4991 except AttributeError:
4992 return ""
4993"#;
4994 let rust = transpile(code);
4995 assert!(rust.contains("fn string_transform"), "output: {}", rust);
4996 }
4997
4998 #[test]
4999 fn test_s9b6_list_ops_in_try() {
5000 let code = r#"
5001def list_builder(n: int) -> list:
5002 try:
5003 items = []
5004 items.append(n)
5005 items.append(n * 2)
5006 return items
5007 except:
5008 return []
5009"#;
5010 let rust = transpile(code);
5011 assert!(rust.contains("fn list_builder"), "output: {}", rust);
5012 }
5013
5014 #[test]
5015 fn test_s9b6_nested_try_blocks() {
5016 let code = r#"
5017def nested_try(s: str) -> int:
5018 try:
5019 outer = int(s)
5020 try:
5021 inner = outer * 2
5022 return inner
5023 except:
5024 return outer
5025 except ValueError:
5026 return 0
5027"#;
5028 let rust = transpile(code);
5029 assert!(rust.contains("fn nested_try"), "output: {}", rust);
5030 }
5031
5032 #[test]
5033 fn test_s9b6_assignment_in_try_no_annotation() {
5034 let code = r#"
5035def infer_type(s: str) -> int:
5036 try:
5037 result = int(s)
5038 except ValueError:
5039 result = 0
5040 return result
5041"#;
5042 let rust = transpile(code);
5043 assert!(rust.contains("fn infer_type"), "output: {}", rust);
5044 }
5045
5046 #[test]
5047 fn test_s9b6_nested_function_returns_dict() {
5048 let code = r#"
5049def make_config() -> dict:
5050 def builder() -> dict:
5051 config = {}
5052 config["key"] = "value"
5053 return config
5054 return builder()
5055"#;
5056 let rust = transpile(code);
5057 assert!(rust.contains("fn make_config"), "output: {}", rust);
5058 }
5059
5060 #[test]
5061 fn test_s9b6_nested_function_with_capture_via_with() {
5062 let code = r#"
5063def with_context(filename: str) -> str:
5064 with open(filename) as f:
5065 def reader() -> str:
5066 return f.read()
5067 return reader()
5068"#;
5069 let rust = transpile(code);
5070 assert!(rust.contains("fn with_context"), "output: {}", rust);
5071 }
5072
5073 #[test]
5074 fn test_s9b6_nested_function_try_except_capture() {
5075 let code = r#"
5076def error_handler(s: str) -> int:
5077 try:
5078 base = int(s)
5079 def multiply() -> int:
5080 return base * 2
5081 return multiply()
5082 except ValueError:
5083 return 0
5084"#;
5085 let rust = transpile(code);
5086 assert!(rust.contains("fn error_handler"), "output: {}", rust);
5087 }
5088
5089 #[test]
5090 fn test_s9b6_try_with_finally_and_else() {
5091 let code = r#"
5092def full_try(s: str) -> int:
5093 try:
5094 x = int(s)
5095 except ValueError:
5096 return -1
5097 else:
5098 return x
5099 finally:
5100 print("cleanup")
5101"#;
5102 let rust = transpile(code);
5103 assert!(rust.contains("fn full_try"), "output: {}", rust);
5104 }
5105
5106 #[test]
5107 fn test_s9b6_recursive_nested_function_no_captures() {
5108 let code = r#"
5109def outer() -> int:
5110 def fibonacci(n: int) -> int:
5111 if n <= 1:
5112 return n
5113 return fibonacci(n - 1) + fibonacci(n - 2)
5114 return fibonacci(5)
5115"#;
5116 let rust = transpile(code);
5117 assert!(rust.contains("fn outer"), "output: {}", rust);
5118 }
5119
5120 #[test]
5121 fn test_s9b6_try_else_with_list_return() {
5122 let code = r#"
5123def parse_list(items: list) -> list:
5124 result = []
5125 try:
5126 for item in items:
5127 result.append(int(item))
5128 except ValueError:
5129 return []
5130 else:
5131 return result
5132"#;
5133 let rust = transpile(code);
5134 assert!(rust.contains("fn parse_list"), "output: {}", rust);
5135 }
5136
5137 #[test]
5138 fn test_s9b6_with_stmt_args_field_access() {
5139 let code = r#"
5140def process_file(args) -> str:
5141 with open(args.filename) as f:
5142 return f.read()
5143"#;
5144 let rust = transpile(code);
5145 assert!(rust.contains("fn process_file"), "output: {}", rust);
5146 }
5147
5148 #[test]
5149 fn test_s9b6_try_else_finally_args_access() {
5150 let code = r#"
5151def process_input(args) -> int:
5152 try:
5153 result = int(args.value)
5154 except ValueError:
5155 return 0
5156 else:
5157 return result
5158 finally:
5159 print(args.verbose)
5160"#;
5161 let rust = transpile(code);
5162 assert!(rust.contains("fn process_input"), "output: {}", rust);
5163 }
5164
5165 #[test]
5166 fn test_s9b6_nested_function_returns_tuple() {
5167 let code = r#"
5168def make_pair(x: int, y: int) -> tuple:
5169 def pair() -> tuple:
5170 return (x, y)
5171 return pair()
5172"#;
5173 let rust = transpile(code);
5174 assert!(rust.contains("fn make_pair"), "output: {}", rust);
5175 }
5176
5177 #[test]
5178 fn test_s9b6_try_with_break_continue() {
5179 let code = r#"
5180def find_valid(items: list) -> int:
5181 for item in items:
5182 try:
5183 x = int(item)
5184 if x > 0:
5185 return x
5186 continue
5187 except ValueError:
5188 break
5189 return 0
5190"#;
5191 let rust = transpile(code);
5192 assert!(rust.contains("fn find_valid"), "output: {}", rust);
5193 }
5194
5195 #[test]
5196 fn test_s9b6_multiple_nested_functions() {
5197 let code = r#"
5198def outer() -> int:
5199 def add(a: int, b: int) -> int:
5200 return a + b
5201 def multiply(a: int, b: int) -> int:
5202 return a * b
5203 return add(2, 3) + multiply(4, 5)
5204"#;
5205 let rust = transpile(code);
5206 assert!(rust.contains("fn outer"), "output: {}", rust);
5207 }
5208
5209 #[test]
5210 fn test_s9b6_try_except_with_walrus() {
5211 let code = r#"
5212def walrus_try(s: str) -> int:
5213 try:
5214 if (x := int(s)) > 0:
5215 return x
5216 except ValueError:
5217 return 0
5218 return -1
5219"#;
5220 let rust = transpile(code);
5221 assert!(rust.contains("fn walrus_try"), "output: {}", rust);
5222 }
5223
5224 #[test]
5225 fn test_s9b6_nested_function_with_nested_loop() {
5226 let code = r#"
5227def matrix_sum(rows: list) -> int:
5228 def sum_row(row: list) -> int:
5229 total = 0
5230 for item in row:
5231 total += item
5232 return total
5233 result = 0
5234 for row in rows:
5235 result += sum_row(row)
5236 return result
5237"#;
5238 let rust = transpile(code);
5239 assert!(rust.contains("fn matrix_sum"), "output: {}", rust);
5240 }
5241
5242 #[test]
5243 fn test_s9b6_try_except_type_annotation_escape() {
5244 let code = r#"
5245def annotated_escape(s: str) -> int:
5246 x: int
5247 try:
5248 x = int(s)
5249 except ValueError:
5250 x = 0
5251 return x
5252"#;
5253 let rust = transpile(code);
5254 assert!(rust.contains("fn annotated_escape"), "output: {}", rust);
5255 }
5256
5257 #[test]
5258 fn test_s9b6_nested_try_with_else() {
5259 let code = r#"
5260def nested_else(s: str) -> str:
5261 try:
5262 x = int(s)
5263 try:
5264 y = x * 2
5265 except:
5266 return "inner error"
5267 else:
5268 return "inner success"
5269 except ValueError:
5270 return "outer error"
5271"#;
5272 let rust = transpile(code);
5273 assert!(rust.contains("fn nested_else"), "output: {}", rust);
5274 }
5275
5276 #[test]
5277 fn test_s9b6_exception_tuple_handler() {
5278 let code = r#"
5279def tuple_catch(s: str) -> int:
5280 try:
5281 return int(s)
5282 except (ValueError, TypeError, AttributeError):
5283 return -1
5284"#;
5285 let rust = transpile(code);
5286 assert!(rust.contains("fn tuple_catch"), "output: {}", rust);
5287 }
5288
5289 #[test]
5290 fn test_s9b6_nested_function_bool_return() {
5291 let code = r#"
5292def outer() -> bool:
5293 x = 10
5294 def check() -> bool:
5295 return x > 5
5296 return check()
5297"#;
5298 let rust = transpile(code);
5299 assert!(rust.contains("fn outer"), "output: {}", rust);
5300 }
5301
5302 #[test]
5303 fn test_s9b6_try_finally_with_return_in_finally() {
5304 let code = r#"
5305def finally_return(s: str) -> int:
5306 try:
5307 x = int(s)
5308 return x
5309 finally:
5310 return 0
5311"#;
5312 let rust = transpile(code);
5313 assert!(rust.contains("fn finally_return"), "output: {}", rust);
5314 }
5315
5316 #[test]
5317 fn test_s9b6_complex_nested_capture() {
5318 let code = r#"
5319def complex_capture(base: int) -> int:
5320 multiplier = 2
5321 def level1() -> int:
5322 def level2() -> int:
5323 return base * multiplier
5324 return level2() + base
5325 return level1()
5326"#;
5327 let rust = transpile(code);
5328 assert!(rust.contains("fn complex_capture"), "output: {}", rust);
5329 }
5330
5331 #[test]
5332 fn test_s9b6_try_except_with_dict_ops() {
5333 let code = r#"
5334def dict_ops(key: str) -> dict:
5335 try:
5336 result = {}
5337 result[key] = "value"
5338 result["count"] = 1
5339 return result
5340 except:
5341 return {}
5342"#;
5343 let rust = transpile(code);
5344 assert!(rust.contains("fn dict_ops"), "output: {}", rust);
5345 }
5346}