1use anyhow::Result;
18use rustpython_ast::{self as ast, Ranged};
19use std::cell::RefCell;
20use std::collections::{HashMap, HashSet};
21use std::rc::Rc;
22use thiserror::Error;
23use tracing;
24
25#[derive(Debug, Error)]
26pub enum TypeIntrospectionError {
27 #[error("Pyright query failed: {0}")]
28 PyrightError(String),
29
30 #[error("Mypy query failed: {0}")]
31 MypyError(String),
32
33 #[error("Failed to determine position in source: {0}")]
34 PositionError(String),
35
36 #[error("No type introspection client available")]
37 NoClientAvailable,
38}
39
40use crate::ast_utils;
41use crate::core::{ParameterInfo, ReplaceInfo};
42use crate::type_introspection_context::TypeIntrospectionContext;
43use crate::types::TypeIntrospectionMethod;
44
45pub struct RustPythonFunctionCallReplacer<'a> {
47 replacements_info: HashMap<String, ReplaceInfo>,
48 replacements: Vec<(std::ops::Range<usize>, String)>, type_introspection: TypeIntrospectionMethod,
50 file_path: String,
51 module_name: String,
52 import_map: HashMap<String, String>,
53 source_content: String,
54 inheritance_map: HashMap<String, Vec<String>>,
55 pyright_client: Option<Rc<RefCell<Box<dyn crate::pyright_lsp::PyrightLspClientTrait>>>>,
56 mypy_client: Option<Rc<RefCell<crate::mypy_lsp::MypyTypeIntrospector>>>,
57 type_cache: RefCell<HashMap<(u32, u32), Option<String>>>,
58 _type_context: &'a mut TypeIntrospectionContext,
59 builtins: HashSet<String>,
60 module_functions: HashSet<String>, }
62
63#[allow(dead_code)] impl<'a> RustPythonFunctionCallReplacer<'a> {
65 pub fn new_with_context(
66 replacements_info: HashMap<String, ReplaceInfo>,
67 type_introspection_context: &'a mut TypeIntrospectionContext,
68 file_path: String,
69 module_name: String,
70 source_content: String,
71 inheritance_map: HashMap<String, Vec<String>>,
72 builtins: HashSet<String>,
73 ) -> Result<Self> {
74 let type_introspection = type_introspection_context.method();
75
76 let pyright_client = type_introspection_context.pyright_client();
78 let mypy_client = type_introspection_context.mypy_client();
79
80 Ok(Self {
81 replacements_info,
82 replacements: Vec::new(),
83 type_introspection,
84 file_path,
85 module_name,
86 import_map: HashMap::new(),
87 source_content,
88 inheritance_map,
89 pyright_client,
90 mypy_client,
91 type_cache: RefCell::new(HashMap::new()),
92 _type_context: type_introspection_context,
93 builtins,
94 module_functions: HashSet::new(),
95 })
96 }
97
98 pub fn get_replacements(mut self) -> Vec<(std::ops::Range<usize>, String)> {
99 std::mem::take(&mut self.replacements)
100 }
101
102 pub fn visit_module(&mut self, module: &[ast::Stmt]) {
104 tracing::debug!("Visiting module with {} statements", module.len());
105
106 for stmt in module {
108 if let ast::Stmt::ImportFrom(import) = stmt {
109 let level = import.level.as_ref().map_or(0, |i| i.to_usize());
110 if let Some(module) = &import.module {
111 let resolved_module = if level > 0 {
113 let module_parts: Vec<&str> = self.module_name.split('.').collect();
115 let parent_parts = if module_parts.len() > level {
117 &module_parts[..module_parts.len() - level]
118 } else {
119 &module_parts[..1]
121 };
122
123 let parent = parent_parts.join(".");
124 if module.is_empty() {
125 parent
127 } else {
128 format!("{}.{}", parent, module)
130 }
131 } else {
132 module.to_string()
133 };
134
135 for alias in &import.names {
136 let full_name = format!("{}.{}", resolved_module, alias.name);
137 if alias.asname.is_none() {
139 self.import_map.insert(alias.name.to_string(), full_name);
140 }
141 }
142 }
143 }
144 }
145
146 for stmt in module {
148 match stmt {
149 ast::Stmt::FunctionDef(func) => {
150 self.module_functions.insert(func.name.to_string());
151 }
152 ast::Stmt::AsyncFunctionDef(func) => {
153 self.module_functions.insert(func.name.to_string());
154 }
155 ast::Stmt::ClassDef(class) => {
156 for body_stmt in &class.body {
158 if let ast::Stmt::FunctionDef(func) = body_stmt {
159 self.module_functions
161 .insert(format!("{}.{}", class.name, func.name));
162 }
163 }
164 }
165 _ => {}
166 }
167 }
168
169 tracing::debug!(
170 "Found {} module functions: {:?}",
171 self.module_functions.len(),
172 self.module_functions
173 );
174
175 for (i, stmt) in module.iter().enumerate() {
177 tracing::debug!("Visiting statement {}", i);
178 self.visit_stmt(stmt);
179 }
180 }
181
182 fn visit_stmt(&mut self, stmt: &ast::Stmt) {
184 match stmt {
185 ast::Stmt::FunctionDef(func) => {
186 let has_replace_me = func.decorator_list.iter().any(|dec| match dec {
188 ast::Expr::Name(name) => name.id.as_str() == "replace_me",
189 ast::Expr::Call(call) => {
190 if let ast::Expr::Name(name) = &*call.func {
191 name.id.as_str() == "replace_me"
192 } else {
193 false
194 }
195 }
196 _ => false,
197 });
198
199 if !has_replace_me {
200 for s in &func.body {
201 self.visit_stmt(s);
202 }
203 }
204 }
205 ast::Stmt::AsyncFunctionDef(func) => {
206 tracing::debug!("Visiting async function: {}", func.name);
207 for s in &func.body {
208 self.visit_stmt(s);
209 }
210 }
211 ast::Stmt::ClassDef(class) => {
212 for s in &class.body {
213 self.visit_stmt(s);
214 }
215 }
216 ast::Stmt::For(for_stmt) => {
217 self.visit_expr(&for_stmt.target);
219 self.visit_expr(&for_stmt.iter);
220 for s in &for_stmt.body {
221 self.visit_stmt(s);
222 }
223 for s in &for_stmt.orelse {
224 self.visit_stmt(s);
225 }
226 }
227 ast::Stmt::While(while_stmt) => {
228 self.visit_expr(&while_stmt.test);
230 for s in &while_stmt.body {
231 self.visit_stmt(s);
232 }
233 for s in &while_stmt.orelse {
234 self.visit_stmt(s);
235 }
236 }
237 ast::Stmt::If(if_stmt) => {
238 self.visit_expr(&if_stmt.test);
240 for s in &if_stmt.body {
241 self.visit_stmt(s);
242 }
243 for s in &if_stmt.orelse {
244 self.visit_stmt(s);
245 }
246 }
247 ast::Stmt::With(with) => {
248 for item in &with.items {
250 self.visit_expr(&item.context_expr);
251 if let Some(optional_vars) = &item.optional_vars {
252 self.visit_expr(optional_vars);
253 }
254 }
255 for s in &with.body {
256 self.visit_stmt(s);
257 }
258 }
259 ast::Stmt::AsyncWith(with) => {
260 for item in &with.items {
262 self.visit_expr(&item.context_expr);
263 if let Some(optional_vars) = &item.optional_vars {
264 self.visit_expr(optional_vars);
265 }
266 }
267 for s in &with.body {
268 self.visit_stmt(s);
269 }
270 }
271 ast::Stmt::Try(try_stmt) => {
272 for s in &try_stmt.body {
273 self.visit_stmt(s);
274 }
275 for handler in &try_stmt.handlers {
276 let ast::ExceptHandler::ExceptHandler(h) = handler;
277 for s in &h.body {
278 self.visit_stmt(s);
279 }
280 }
281 for s in &try_stmt.orelse {
282 self.visit_stmt(s);
283 }
284 for s in &try_stmt.finalbody {
285 self.visit_stmt(s);
286 }
287 }
288 ast::Stmt::Expr(expr_stmt) => {
289 self.visit_expr(&expr_stmt.value);
290 }
291 ast::Stmt::Return(ret) => {
292 if let Some(value) = &ret.value {
293 self.visit_expr(value);
294 }
295 }
296 ast::Stmt::Delete(del) => {
297 for target in &del.targets {
298 self.visit_expr(target);
299 }
300 }
301 ast::Stmt::Assign(assign) => {
302 self.visit_expr(&assign.value);
303 }
304 ast::Stmt::AugAssign(aug) => {
305 self.visit_expr(&aug.value);
306 }
307 ast::Stmt::AnnAssign(ann) => {
308 if let Some(value) = &ann.value {
309 self.visit_expr(value);
310 }
311 }
312 ast::Stmt::Import(import) => {
313 for alias in &import.names {
314 if let Some(asname) = &alias.asname {
315 self.import_map
316 .insert(asname.to_string(), alias.name.to_string());
317 } else {
318 let parts: Vec<&str> = alias.name.split('.').collect();
319 if let Some(first) = parts.first() {
320 self.import_map
321 .insert(first.to_string(), alias.name.to_string());
322 }
323 }
324 }
325 }
326 ast::Stmt::ImportFrom(_) => {
327 }
329 _ => {}
330 }
331 }
332
333 fn visit_expr(&mut self, expr: &ast::Expr) {
335 match expr {
336 ast::Expr::Call(call) => {
337 let func_name = self.expr_to_string(&call.func);
338 tracing::info!("Visiting call expression: {}", func_name);
339
340 if let Some(replacement_str) = self.check_magic_method_call_direct(expr, call) {
342 tracing::debug!("Found magic method replacement");
343 let range = expr.range();
344 let source_range = range.start().to_usize()..range.end().to_usize();
345 self.replacements.push((source_range, replacement_str));
346 return;
347 }
348
349 if let Some(replacement_str) =
351 self.check_function_call_direct(&call.func, &call.args, &call.keywords)
352 {
353 tracing::debug!(
354 "Found function replacement for {}: {}",
355 func_name,
356 replacement_str
357 );
358 let range = expr.range();
359 let source_range = range.start().to_usize()..range.end().to_usize();
360 tracing::debug!("Adding replacement at range {:?}", source_range);
361 self.replacements.push((source_range, replacement_str));
362 return;
363 }
364
365 self.visit_expr(&call.func);
367 for arg in &call.args {
368 self.visit_expr(arg);
369 }
370 for keyword in &call.keywords {
371 self.visit_expr(&keyword.value);
372 }
373 }
374 ast::Expr::BinOp(binop) => {
375 tracing::debug!(
376 "Visiting binary operation: {} op {}",
377 self.expr_to_string(&binop.left),
378 self.expr_to_string(&binop.right)
379 );
380 self.visit_expr(&binop.left);
381 self.visit_expr(&binop.right);
382 }
383 ast::Expr::UnaryOp(unary) => {
384 self.visit_expr(&unary.operand);
385 }
386 ast::Expr::IfExp(ifexp) => {
387 self.visit_expr(&ifexp.test);
388 self.visit_expr(&ifexp.body);
389 self.visit_expr(&ifexp.orelse);
390 }
391 ast::Expr::Dict(dict) => {
392 for key in dict.keys.iter().flatten() {
393 self.visit_expr(key);
394 }
395 for value in &dict.values {
396 self.visit_expr(value);
397 }
398 }
399 ast::Expr::Set(set) => {
400 for elt in &set.elts {
401 self.visit_expr(elt);
402 }
403 }
404 ast::Expr::List(list) => {
405 for elt in &list.elts {
406 self.visit_expr(elt);
407 }
408 }
409 ast::Expr::Tuple(tuple) => {
410 for elt in &tuple.elts {
411 self.visit_expr(elt);
412 }
413 }
414 ast::Expr::ListComp(comp) => {
415 self.visit_expr(&comp.elt);
416 for gen in &comp.generators {
417 self.visit_expr(&gen.iter);
418 for if_expr in &gen.ifs {
419 self.visit_expr(if_expr);
420 }
421 }
422 }
423 ast::Expr::SetComp(comp) => {
424 self.visit_expr(&comp.elt);
425 for gen in &comp.generators {
426 self.visit_expr(&gen.iter);
427 for if_expr in &gen.ifs {
428 self.visit_expr(if_expr);
429 }
430 }
431 }
432 ast::Expr::DictComp(comp) => {
433 self.visit_expr(&comp.key);
434 self.visit_expr(&comp.value);
435 for gen in &comp.generators {
436 self.visit_expr(&gen.iter);
437 for if_expr in &gen.ifs {
438 self.visit_expr(if_expr);
439 }
440 }
441 }
442 ast::Expr::GeneratorExp(comp) => {
443 self.visit_expr(&comp.elt);
444 for gen in &comp.generators {
445 self.visit_expr(&gen.iter);
446 for if_expr in &gen.ifs {
447 self.visit_expr(if_expr);
448 }
449 }
450 }
451 ast::Expr::Lambda(lambda) => {
452 self.visit_expr(&lambda.body);
453 }
454 ast::Expr::Subscript(sub) => {
455 self.visit_expr(&sub.value);
456 self.visit_expr(&sub.slice);
457 }
458 ast::Expr::Compare(comp) => {
459 self.visit_expr(&comp.left);
460 for comparator in &comp.comparators {
461 self.visit_expr(comparator);
462 }
463 }
464 ast::Expr::NamedExpr(named) => {
465 tracing::debug!(
466 "Visiting NamedExpr, value: {:?}",
467 self.expr_to_string(&named.value)
468 );
469 self.visit_expr(&named.target);
470 self.visit_expr(&named.value);
471 }
472 ast::Expr::Await(await_expr) => {
473 tracing::debug!("Visiting await expression");
474
475 if let ast::Expr::Call(call) = &*await_expr.value {
477 if let Some(replacement_str) =
479 self.check_function_call_direct(&call.func, &call.args, &call.keywords)
480 {
481 let range = expr.range();
484 let source_range = range.start().to_usize()..range.end().to_usize();
485 self.replacements.push((source_range, replacement_str));
486 return;
487 }
488 }
489
490 self.visit_expr(&await_expr.value);
492 }
493 _ => {}
494 }
495 }
496
497 fn check_magic_method_call_direct(
499 &mut self,
500 _call_expr: &ast::Expr,
501 call: &ast::ExprCall,
502 ) -> Option<String> {
503 if call.args.len() == 1 {
505 if let ast::Expr::Name(name) = &*call.func {
506 let builtin_name = name.id.as_str();
507 tracing::debug!("Checking builtin function: {}", builtin_name);
508
509 let magic_method = match builtin_name {
510 "int" => "__int__",
511 "str" => "__str__",
512 "float" => "__float__",
513 "bool" => "__bool__",
514 "len" => "__len__",
515 "abs" => "__abs__",
516 "hash" => "__hash__",
517 "bytes" => "__bytes__",
518 "format" => "__format__",
519 "repr" => "__repr__",
520 _ => {
521 tracing::debug!("Unknown builtin: {}", builtin_name);
522 return None;
523 }
524 };
525 tracing::debug!("Matched magic method: {}", magic_method);
526
527 let type_name = match self.get_expression_type(&call.args[0]) {
529 Ok(Some(t)) => t,
530 Ok(None) => {
531 tracing::debug!(
532 "No type information available for {} expression",
533 builtin_name
534 );
535 return None;
536 }
537 Err(e) => {
538 tracing::debug!(
539 "Failed to get type for {} expression: {}",
540 builtin_name,
541 e
542 );
543 return None;
544 }
545 };
546
547 tracing::debug!("Type of expression for {}: {}", builtin_name, type_name);
548 let method_key = format!("{}.{}", type_name, magic_method);
552 let method_key_with_module = if !type_name.contains('.') {
553 Some(format!(
554 "{}.{}.{}",
555 self.module_name, type_name, magic_method
556 ))
557 } else {
558 None
559 };
560
561 let replace_info = self.replacements_info.get(&method_key).or_else(|| {
562 method_key_with_module
563 .as_ref()
564 .and_then(|k| self.replacements_info.get(k))
565 });
566
567 if let Some(info) = replace_info {
568 tracing::debug!(
569 "Found replacement info for {}: {}",
570 method_key,
571 info.replacement_expr
572 );
573
574 let replacement_ast = info
576 .replacement_ast
577 .as_ref()
578 .expect("replacement_ast must be present for proper transformation");
579
580 let source_module = info
584 .old_name
585 .find('.')
586 .map(|first_dot_pos| &info.old_name[..first_dot_pos]);
587
588 let replacement = self.walk_replacement_ast(
589 replacement_ast,
590 &call.args,
591 &call.keywords,
592 &info.parameters,
593 source_module,
594 );
595
596 let replacement = if let Some(src_mod) = source_module {
598 if src_mod == self.module_name {
599 replacement.replace(&format!("{}.", src_mod), "")
601 } else {
602 replacement
603 }
604 } else {
605 replacement
606 };
607
608 if self.should_unwrap_magic_method_call(replacement_ast, builtin_name) {
611 if let rustpython_ast::Expr::Call(builtin_call) = replacement_ast.as_ref() {
613 if builtin_call.args.len() == 1 {
614 let inner_replacement = self.walk_replacement_ast_impl(
616 &builtin_call.args[0],
617 &call.args,
618 &call.keywords,
619 &info.parameters,
620 source_module,
621 );
622 return Some(inner_replacement);
623 }
624 }
625 }
626
627 return Some(replacement);
628 } else {
629 tracing::debug!("No replacement found for {}", method_key);
630 return None;
631 }
632 }
633 }
634 None
635 }
636
637 fn check_function_call_direct(
639 &mut self,
640 func: &ast::Expr,
641 args: &[ast::Expr],
642 keywords: &[ast::Keyword],
643 ) -> Option<String> {
644 let func_name = self.get_func_name(func)?;
646 println!(
647 "DEBUG: check_function_call_direct - func_name={}",
648 func_name
649 );
650
651 let replace_info = self.replacements_info.get(&func_name);
653 if replace_info.is_none() {
654 println!("DEBUG: No replacement found for {}", func_name);
655 println!(
656 "DEBUG: Available replacements: {:?}",
657 self.replacements_info.keys().collect::<Vec<_>>()
658 );
659 return None;
660 }
661 let replace_info = replace_info.unwrap();
662
663 if let Some(ref replacement_ast) = replace_info.replacement_ast {
665 let mut param_map = HashMap::new();
667
668 let is_method_call = matches!(func, ast::Expr::Attribute(_)) &&
671 matches!(replace_info.construct_type,
672 crate::core::ConstructType::ClassMethod |
673 crate::core::ConstructType::Function |
674 crate::core::ConstructType::AsyncFunction
675 ) &&
676 (replace_info.construct_type != crate::core::ConstructType::Function ||
679 replace_info.parameters.first().is_some_and(|p| p.name == "self"));
680
681 if is_method_call {
682 if let ast::Expr::Attribute(attr_expr) = func {
684 match replace_info.construct_type {
685 crate::core::ConstructType::ClassMethod => {
686 if let Some(first_param) = replace_info.parameters.first() {
688 if !first_param.is_vararg && !first_param.is_kwarg {
689 param_map.insert(first_param.name.clone(), &*attr_expr.value);
690 }
691 }
692
693 for (i, arg) in args.iter().enumerate() {
695 if let Some(param) = replace_info.parameters.get(i + 1) {
696 if !param.is_vararg && !param.is_kwarg {
697 param_map.insert(param.name.clone(), arg);
698 }
699 }
700 }
701 }
702 crate::core::ConstructType::StaticMethod => {
703 for (i, arg) in args.iter().enumerate() {
705 if let Some(param) = replace_info.parameters.get(i) {
706 if !param.is_vararg && !param.is_kwarg {
707 param_map.insert(param.name.clone(), arg);
708 }
709 }
710 }
711 }
712 _ => {
713 if let Some(first_param) = replace_info.parameters.first() {
715 if !first_param.is_vararg && !first_param.is_kwarg {
716 param_map.insert(first_param.name.clone(), &*attr_expr.value);
717 }
718 }
719
720 for (i, arg) in args.iter().enumerate() {
722 if let Some(param) = replace_info.parameters.get(i + 1) {
723 if !param.is_vararg && !param.is_kwarg {
724 param_map.insert(param.name.clone(), arg);
725 }
726 }
727 }
728 }
729 }
730 }
731 } else {
732 let mut regular_params = Vec::new();
734 let mut vararg_param = None;
735 let mut kwarg_param = None;
736
737 for param in &replace_info.parameters {
738 if param.is_vararg {
739 vararg_param = Some(param.name.clone());
740 } else if param.is_kwarg {
741 kwarg_param = Some(param.name.clone());
742 } else {
743 regular_params.push(param);
744 }
745 }
746
747 let has_dict_unpack = keywords.iter().any(|kw| kw.arg.is_none());
749 let has_kwarg_param = kwarg_param.is_some();
750
751 if has_dict_unpack && !has_kwarg_param {
752 for (i, arg) in args.iter().enumerate() {
755 if i < regular_params.len() {
756 param_map.insert(regular_params[i].name.clone(), arg);
757 }
758 }
759 } else {
761 for (i, arg) in args.iter().enumerate() {
763 if i < regular_params.len() {
764 param_map.insert(regular_params[i].name.clone(), arg);
765 } else if vararg_param.is_some() {
766 break;
768 }
769 }
770
771 for keyword in keywords {
773 if let Some(arg_name) = &keyword.arg {
774 param_map.insert(arg_name.to_string(), &keyword.value);
775 }
776 }
777 }
778 }
779
780 let mut augmented_args = Vec::new();
783 let mut augmented_keywords = Vec::new();
784
785 if is_method_call {
786 if let ast::Expr::Attribute(attr_expr) = func {
788 augmented_args.push((*attr_expr.value).clone());
789 }
790 augmented_args.extend_from_slice(args);
792 } else {
793 augmented_args.extend_from_slice(args);
795 }
796
797 augmented_keywords.extend_from_slice(keywords);
799
800 let source_module = replace_info
803 .old_name
804 .find('.')
805 .map(|first_dot_pos| &replace_info.old_name[..first_dot_pos]);
806
807 let replacement_str = self.walk_replacement_ast(
808 replacement_ast,
809 &augmented_args,
810 &augmented_keywords,
811 &replace_info.parameters,
812 source_module,
813 );
814
815 let replacement_str = if let Some(src_mod) = source_module {
817 if src_mod == self.module_name {
818 replacement_str.replace(&format!("{}.", src_mod), "")
820 } else {
821 replacement_str
822 }
823 } else {
824 replacement_str
825 };
826
827 return Some(replacement_str);
828 }
829
830 let mut arg_map = HashMap::new();
833
834 let is_method_call = matches!(func, ast::Expr::Attribute(_));
836
837 if is_method_call {
838 if let ast::Expr::Attribute(attr_expr) = func {
840 let self_str = self.expr_to_string(&attr_expr.value);
841 arg_map.insert("self".to_string(), self_str);
842
843 let method_params: Vec<_> = replace_info
845 .parameters
846 .iter()
847 .filter(|p| p.name != "self")
848 .collect();
849
850 for (i, arg) in args.iter().enumerate() {
851 if i < method_params.len() {
852 let param = method_params[i];
853 let arg_str = self.expr_to_string(arg);
854 arg_map.insert(param.name.clone(), arg_str);
855 }
856 }
857 }
858 } else {
859 for (i, arg) in args.iter().enumerate() {
861 if i < replace_info.parameters.len() {
862 let param = &replace_info.parameters[i];
863 let arg_str = self.expr_to_string(arg);
864 arg_map.insert(param.name.clone(), arg_str);
865 }
866 }
867 }
868
869 for keyword in keywords {
871 if let Some(arg_name) = &keyword.arg {
872 let arg_str = self.expr_to_string(&keyword.value);
873 arg_map.insert(arg_name.to_string(), arg_str);
874 }
875 }
876
877 let mut replacement = replace_info.replacement_expr.clone();
879 for (param_name, arg_value) in arg_map {
880 let placeholder = format!("{{{}}}", param_name);
881 replacement = replacement.replace(&placeholder, &arg_value);
882 }
883
884 Some(replacement)
885 }
886
887 fn get_func_name(&mut self, func: &ast::Expr) -> Option<String> {
889 match func {
890 ast::Expr::Name(name) => {
891 if let Some(full_name) = self.import_map.get(name.id.as_str()) {
893 Some(full_name.clone())
894 } else {
895 let full_name = format!("{}.{}", self.module_name, name.id);
897 #[allow(clippy::map_entry)]
898 if self.replacements_info.contains_key(&full_name) {
900 Some(full_name)
901 } else {
902 Some(name.id.to_string())
903 }
904 }
905 }
906 ast::Expr::Attribute(attr_expr) => {
907 let base_str = self.expr_to_string(&attr_expr.value);
909
910 let resolved_base = if let Some(imported_path) = self.import_map.get(&base_str) {
912 imported_path.clone()
913 } else {
914 base_str.clone()
915 };
916
917 let full_path = format!("{}.{}", resolved_base, attr_expr.attr);
918
919 if self.replacements_info.contains_key(&full_path) {
921 tracing::debug!("Found replacement for module function: {}", full_path);
922 return Some(full_path);
923 }
924
925 tracing::debug!(
928 "Attempting to get type for attribute base: {:?}",
929 self.expr_to_string(&attr_expr.value)
930 );
931 let type_result = self.get_expression_type(&attr_expr.value);
932 println!("DEBUG: get_expression_type result: {:?}", type_result);
933 if let Ok(Some(base_type)) = type_result {
934 tracing::debug!("Got type for attribute base: {}", base_type);
935
936 let full_type = if let Some(imported_path) = self.import_map.get(&base_type) {
938 tracing::debug!("Type {} was imported as {}", base_type, imported_path);
939 imported_path.clone()
940 } else if base_type.contains('.') {
941 base_type.clone()
943 } else {
944 let result = format!("{}.{}", self.module_name, base_type);
946 result
947 };
948
949 let full_method_name = format!("{}.{}", full_type, attr_expr.attr);
950 tracing::debug!("Checking for method replacement: {}", full_method_name);
951
952 if self.replacements_info.contains_key(&full_method_name) {
953 tracing::debug!("Found replacement for method: {}", full_method_name);
954 return Some(full_method_name);
955 }
956
957 tracing::debug!(
959 "Checking inheritance map for {}: {:?}",
960 full_type,
961 self.inheritance_map.get(&full_type)
962 );
963
964 let mut parent_classes = self.inheritance_map.get(&full_type);
966
967 if parent_classes.is_none() && !full_type.starts_with("dulwich.") {
969 for (full_class_name, parents) in &self.inheritance_map {
971 if full_class_name
973 .ends_with(&format!(".{}", full_type.replace("repo.", "")))
974 {
975 parent_classes = Some(parents);
976 break;
977 }
978 }
979 }
980
981 if let Some(parent_classes) = parent_classes {
982 for parent in parent_classes {
983 let parent_method_name = format!("{}.{}", parent, attr_expr.attr);
985 tracing::debug!("Checking parent class method: {}", parent_method_name);
986 if self.replacements_info.contains_key(&parent_method_name) {
987 tracing::debug!(
988 "Found replacement in parent class: {}",
989 parent_method_name
990 );
991 return Some(parent_method_name);
992 }
993 }
994 }
995
996 let method_name = format!("{}.{}", base_type, attr_expr.attr);
998 if self.replacements_info.contains_key(&method_name) {
999 return Some(method_name);
1000 }
1001 }
1002
1003 Some(full_path)
1005 }
1006 _ => None,
1007 }
1008 }
1009
1010 fn get_expression_type(
1012 &mut self,
1013 expr: &ast::Expr,
1014 ) -> Result<Option<String>, TypeIntrospectionError> {
1015 let range = expr.range();
1017 let start_byte = range.start().to_usize();
1018 let end_byte = range.end().to_usize();
1019
1020 let query_byte = match expr {
1022 ast::Expr::Attribute(_attr_expr) => {
1023 let expr_text =
1026 &self.source_content[start_byte..end_byte.min(self.source_content.len())];
1027 if let Some(dot_pos) = expr_text.rfind('.') {
1028 start_byte + dot_pos + 1
1030 } else {
1031 end_byte.saturating_sub(1)
1033 }
1034 }
1035 _ => start_byte,
1036 };
1037
1038 if query_byte > self.source_content.len() {
1040 return Err(TypeIntrospectionError::PositionError(format!(
1041 "Byte position {} exceeds source length {}",
1042 query_byte,
1043 self.source_content.len()
1044 )));
1045 }
1046
1047 let line = self.source_content[..query_byte].matches('\n').count() + 1;
1049 let line_start = self.source_content[..query_byte]
1050 .rfind('\n')
1051 .map(|p| p + 1)
1052 .unwrap_or(0);
1053 let column = query_byte - line_start;
1054
1055 let position = (line as u32, column as u32);
1056
1057 if let Some(cached) = self.type_cache.borrow().get(&position) {
1064 return Ok(cached.clone());
1065 }
1066
1067 let result = match self.type_introspection {
1069 TypeIntrospectionMethod::PyrightLsp => {
1070 if let Some(pyright) = &self.pyright_client {
1071 match pyright.borrow_mut().query_type(
1072 &self.file_path,
1073 &self.source_content,
1074 line as u32,
1075 column as u32,
1076 ) {
1077 Ok(type_opt) => type_opt,
1078 Err(e) => {
1079 tracing::warn!(
1080 "Pyright type query failed at {}:{}: {}",
1081 line,
1082 column,
1083 e
1084 );
1085 return Err(TypeIntrospectionError::PyrightError(e.to_string()));
1086 }
1087 }
1088 } else {
1089 return Err(TypeIntrospectionError::NoClientAvailable);
1090 }
1091 }
1092 TypeIntrospectionMethod::MypyDaemon => {
1093 if let Some(mypy) = &self.mypy_client {
1094 match mypy
1095 .borrow_mut()
1096 .get_type_at_position(&self.file_path, line, column + 1) {
1098 Ok(type_opt) => type_opt,
1099 Err(e) => {
1100 tracing::warn!("Mypy type query failed at {}:{}: {}", line, column, e);
1101 return Err(TypeIntrospectionError::MypyError(e));
1102 }
1103 }
1104 } else {
1105 return Err(TypeIntrospectionError::NoClientAvailable);
1106 }
1107 }
1108 TypeIntrospectionMethod::PyrightWithMypyFallback => {
1109 let pyright_result = if let Some(pyright) = &self.pyright_client {
1111 match pyright.borrow_mut().query_type(
1112 &self.file_path,
1113 &self.source_content,
1114 line as u32,
1115 column as u32,
1116 ) {
1117 Ok(type_opt) => type_opt,
1118 Err(e) => {
1119 tracing::debug!("Pyright failed, trying Mypy: {}", e);
1120 None
1121 }
1122 }
1123 } else {
1124 None
1125 };
1126
1127 if pyright_result.is_some() {
1128 pyright_result
1129 } else if let Some(mypy) = &self.mypy_client {
1130 match mypy
1131 .borrow_mut()
1132 .get_type_at_position(&self.file_path, line, column + 1) {
1134 Ok(type_opt) => type_opt,
1135 Err(e) => {
1136 tracing::warn!(
1137 "Both Pyright and Mypy failed at {}:{}: {}",
1138 line,
1139 column,
1140 e
1141 );
1142 return Err(TypeIntrospectionError::MypyError(e));
1143 }
1144 }
1145 } else {
1146 return Err(TypeIntrospectionError::NoClientAvailable);
1147 }
1148 }
1149 };
1150
1151 let cleaned_result = result.map(|t| {
1153 if let Some(last_dot) = t.rfind('.') {
1155 t[last_dot + 1..].to_string()
1156 } else {
1157 t
1158 }
1159 });
1160
1161 self.type_cache
1163 .borrow_mut()
1164 .insert(position, cleaned_result.clone());
1165
1166 Ok(cleaned_result)
1167 }
1168
1169 fn expr_to_string(&self, expr: &ast::Expr) -> String {
1171 self.expr_to_string_with_placeholders(expr, &HashSet::new())
1173 }
1174
1175 #[allow(clippy::only_used_in_recursion)] fn expr_to_string_with_placeholders<R>(
1178 &self,
1179 expr: &rustpython_ast::Expr<R>,
1180 param_names: &HashSet<String>,
1181 ) -> String {
1182 match expr {
1183 rustpython_ast::Expr::Name(name) => {
1184 ast_utils::format_name_with_placeholders(&name.id, param_names)
1185 }
1186 rustpython_ast::Expr::Constant(c) => ast_utils::format_constant(&c.value),
1187 rustpython_ast::Expr::Call(call) => {
1188 let func_str = self.expr_to_string_with_placeholders(&*call.func, param_names);
1189 let mut arg_strs = Vec::new();
1190 for arg in &call.args {
1191 arg_strs.push(self.expr_to_string_with_placeholders(arg, param_names));
1192 }
1193 for keyword in &call.keywords {
1194 if let Some(arg) = &keyword.arg {
1195 arg_strs.push(format!(
1196 "{}={}",
1197 arg,
1198 self.expr_to_string_with_placeholders(&keyword.value, param_names)
1199 ));
1200 }
1201 }
1202 format!("{}({})", func_str, arg_strs.join(", "))
1203 }
1204 rustpython_ast::Expr::Attribute(attr) => {
1205 let value_str = self.expr_to_string_with_placeholders(&*attr.value, param_names);
1206 format!("{}.{}", value_str, attr.attr)
1207 }
1208 rustpython_ast::Expr::BinOp(binop) => {
1209 let left = self.expr_to_string_with_placeholders(&*binop.left, param_names);
1210 let right = self.expr_to_string_with_placeholders(&*binop.right, param_names);
1211 let op = match &binop.op {
1212 rustpython_ast::Operator::Add => "+",
1213 rustpython_ast::Operator::Sub => "-",
1214 rustpython_ast::Operator::Mult => "*",
1215 rustpython_ast::Operator::Div => "/",
1216 rustpython_ast::Operator::Mod => "%",
1217 rustpython_ast::Operator::Pow => "**",
1218 rustpython_ast::Operator::FloorDiv => "//",
1219 rustpython_ast::Operator::LShift => "<<",
1220 rustpython_ast::Operator::RShift => ">>",
1221 rustpython_ast::Operator::BitOr => "|",
1222 rustpython_ast::Operator::BitXor => "^",
1223 rustpython_ast::Operator::BitAnd => "&",
1224 rustpython_ast::Operator::MatMult => "@",
1225 };
1226 format!("{} {} {}", left, op, right)
1227 }
1228 rustpython_ast::Expr::BoolOp(boolop) => {
1229 let values: Vec<String> = boolop
1230 .values
1231 .iter()
1232 .map(|v| self.expr_to_string_with_placeholders(v, param_names))
1233 .collect();
1234 let op = match &boolop.op {
1235 rustpython_ast::BoolOp::And => " and ",
1236 rustpython_ast::BoolOp::Or => " or ",
1237 };
1238 values.join(op)
1239 }
1240 rustpython_ast::Expr::UnaryOp(unaryop) => {
1241 let operand = self.expr_to_string_with_placeholders(&*unaryop.operand, param_names);
1242 let op = match &unaryop.op {
1243 rustpython_ast::UnaryOp::Not => "not ",
1244 rustpython_ast::UnaryOp::UAdd => "+",
1245 rustpython_ast::UnaryOp::USub => "-",
1246 rustpython_ast::UnaryOp::Invert => "~",
1247 };
1248 format!("{}{}", op, operand)
1249 }
1250 rustpython_ast::Expr::Compare(compare) => {
1251 let mut result = self.expr_to_string_with_placeholders(&*compare.left, param_names);
1252 for (op, comparator) in compare.ops.iter().zip(&compare.comparators) {
1253 let op_str = match op {
1254 rustpython_ast::CmpOp::Eq => " == ",
1255 rustpython_ast::CmpOp::NotEq => " != ",
1256 rustpython_ast::CmpOp::Lt => " < ",
1257 rustpython_ast::CmpOp::LtE => " <= ",
1258 rustpython_ast::CmpOp::Gt => " > ",
1259 rustpython_ast::CmpOp::GtE => " >= ",
1260 rustpython_ast::CmpOp::Is => " is ",
1261 rustpython_ast::CmpOp::IsNot => " is not ",
1262 rustpython_ast::CmpOp::In => " in ",
1263 rustpython_ast::CmpOp::NotIn => " not in ",
1264 };
1265 result.push_str(op_str);
1266 result
1267 .push_str(&self.expr_to_string_with_placeholders(comparator, param_names));
1268 }
1269 result
1270 }
1271 rustpython_ast::Expr::JoinedStr(joined) => {
1272 let mut result = String::from("f\"");
1274 for value in &joined.values {
1275 match value {
1276 rustpython_ast::Expr::Constant(c) => {
1277 if let ast::Constant::Str(s) = &c.value {
1278 result.push_str(s);
1279 }
1280 }
1281 rustpython_ast::Expr::FormattedValue(fval) => {
1282 result.push('{');
1283 result.push_str(
1284 &self.expr_to_string_with_placeholders(&*fval.value, param_names),
1285 );
1286 if let Some(spec) = &fval.format_spec {
1287 result.push(':');
1288 if let rustpython_ast::Expr::JoinedStr(spec_joined) = &**spec {
1290 for spec_part in &spec_joined.values {
1291 if let rustpython_ast::Expr::Constant(c) = spec_part {
1292 if let ast::Constant::Str(s) = &c.value {
1293 result.push_str(s);
1294 }
1295 }
1296 }
1297 }
1298 }
1299 result.push('}');
1300 }
1301 _ => {}
1302 }
1303 }
1304 result.push('"');
1305 result
1306 }
1307 rustpython_ast::Expr::List(list) => {
1308 let items: Vec<String> = list
1309 .elts
1310 .iter()
1311 .map(|e| self.expr_to_string_with_placeholders(e, param_names))
1312 .collect();
1313 format!("[{}]", items.join(", "))
1314 }
1315 rustpython_ast::Expr::Tuple(tuple) => {
1316 let items: Vec<String> = tuple
1317 .elts
1318 .iter()
1319 .map(|e| self.expr_to_string_with_placeholders(e, param_names))
1320 .collect();
1321 if items.len() == 1 {
1322 format!("({},)", items[0])
1323 } else {
1324 format!("({})", items.join(", "))
1325 }
1326 }
1327 rustpython_ast::Expr::Dict(dict) => {
1328 let mut items = Vec::new();
1329 for (key_opt, value) in dict.keys.iter().zip(&dict.values) {
1330 if let Some(key) = key_opt {
1331 let key_str = self.expr_to_string_with_placeholders(key, param_names);
1332 let value_str = self.expr_to_string_with_placeholders(value, param_names);
1333 items.push(format!("{}: {}", key_str, value_str));
1334 } else {
1335 let value_str = self.expr_to_string_with_placeholders(value, param_names);
1337 items.push(format!("**{}", value_str));
1338 }
1339 }
1340 format!("{{{}}}", items.join(", "))
1341 }
1342 rustpython_ast::Expr::Set(set) => {
1343 let items: Vec<String> = set
1344 .elts
1345 .iter()
1346 .map(|e| self.expr_to_string_with_placeholders(e, param_names))
1347 .collect();
1348 format!("{{{}}}", items.join(", "))
1349 }
1350 rustpython_ast::Expr::Subscript(sub) => {
1351 let value = self.expr_to_string_with_placeholders(&*sub.value, param_names);
1352 let slice = self.expr_to_string_with_placeholders(&*sub.slice, param_names);
1353 format!("{}[{}]", value, slice)
1354 }
1355 rustpython_ast::Expr::Slice(slice) => {
1356 let lower = slice
1357 .lower
1358 .as_ref()
1359 .map(|e| self.expr_to_string_with_placeholders(&**e, param_names))
1360 .unwrap_or_default();
1361 let upper = slice
1362 .upper
1363 .as_ref()
1364 .map(|e| self.expr_to_string_with_placeholders(&**e, param_names))
1365 .unwrap_or_default();
1366 let step = slice
1367 .step
1368 .as_ref()
1369 .map(|e| {
1370 format!(
1371 ":{}",
1372 self.expr_to_string_with_placeholders(&**e, param_names)
1373 )
1374 })
1375 .unwrap_or_default();
1376 format!("{}:{}{}", lower, upper, step)
1377 }
1378 rustpython_ast::Expr::Lambda(lambda) => {
1379 let args: Vec<String> = lambda
1380 .args
1381 .args
1382 .iter()
1383 .map(|arg| arg.def.arg.to_string())
1384 .collect();
1385 let body = self.expr_to_string_with_placeholders(&*lambda.body, param_names);
1386 if args.is_empty() {
1387 format!("lambda: {}", body)
1388 } else {
1389 format!("lambda {}: {}", args.join(", "), body)
1390 }
1391 }
1392 rustpython_ast::Expr::ListComp(comp) => {
1393 let elt = self.expr_to_string_with_placeholders(&*comp.elt, param_names);
1394 let mut comp_str = String::new();
1395 for gen in &comp.generators {
1396 let target = self.expr_to_string_with_placeholders(&gen.target, param_names);
1397 let iter = self.expr_to_string_with_placeholders(&gen.iter, param_names);
1398 let async_keyword = if gen.is_async { "async " } else { "" };
1399 comp_str.push_str(&format!(" {}for {} in {}", async_keyword, target, iter));
1400 for if_clause in &gen.ifs {
1401 let if_expr = self.expr_to_string_with_placeholders(if_clause, param_names);
1402 comp_str.push_str(&format!(" if {}", if_expr));
1403 }
1404 }
1405 format!("[{}{}]", elt, comp_str)
1406 }
1407 rustpython_ast::Expr::SetComp(comp) => {
1408 let elt = self.expr_to_string_with_placeholders(&*comp.elt, param_names);
1409 let mut comp_str = String::new();
1410 for gen in &comp.generators {
1411 let target = self.expr_to_string_with_placeholders(&gen.target, param_names);
1412 let iter = self.expr_to_string_with_placeholders(&gen.iter, param_names);
1413 let async_keyword = if gen.is_async { "async " } else { "" };
1414 comp_str.push_str(&format!(" {}for {} in {}", async_keyword, target, iter));
1415 for if_clause in &gen.ifs {
1416 let if_expr = self.expr_to_string_with_placeholders(if_clause, param_names);
1417 comp_str.push_str(&format!(" if {}", if_expr));
1418 }
1419 }
1420 format!("{{{}{}}}", elt, comp_str)
1421 }
1422 rustpython_ast::Expr::DictComp(comp) => {
1423 let key = self.expr_to_string_with_placeholders(&*comp.key, param_names);
1424 let value = self.expr_to_string_with_placeholders(&*comp.value, param_names);
1425 let mut comp_str = String::new();
1426 for gen in &comp.generators {
1427 let target = self.expr_to_string_with_placeholders(&gen.target, param_names);
1428 let iter = self.expr_to_string_with_placeholders(&gen.iter, param_names);
1429 let async_keyword = if gen.is_async { "async " } else { "" };
1430 comp_str.push_str(&format!(" {}for {} in {}", async_keyword, target, iter));
1431 for if_clause in &gen.ifs {
1432 let if_expr = self.expr_to_string_with_placeholders(if_clause, param_names);
1433 comp_str.push_str(&format!(" if {}", if_expr));
1434 }
1435 }
1436 format!("{{{}: {}{}}}", key, value, comp_str)
1437 }
1438 rustpython_ast::Expr::GeneratorExp(comp) => {
1439 let elt = self.expr_to_string_with_placeholders(&*comp.elt, param_names);
1440 let mut comp_str = String::new();
1441 for gen in &comp.generators {
1442 let target = self.expr_to_string_with_placeholders(&gen.target, param_names);
1443 let iter = self.expr_to_string_with_placeholders(&gen.iter, param_names);
1444 let async_keyword = if gen.is_async { "async " } else { "" };
1445 comp_str.push_str(&format!(" {}for {} in {}", async_keyword, target, iter));
1446 for if_clause in &gen.ifs {
1447 let if_expr = self.expr_to_string_with_placeholders(if_clause, param_names);
1448 comp_str.push_str(&format!(" if {}", if_expr));
1449 }
1450 }
1451 format!("({}{})", elt, comp_str)
1452 }
1453 rustpython_ast::Expr::IfExp(if_exp) => {
1454 let body = self.expr_to_string_with_placeholders(&*if_exp.body, param_names);
1455 let test = self.expr_to_string_with_placeholders(&*if_exp.test, param_names);
1456 let orelse = self.expr_to_string_with_placeholders(&*if_exp.orelse, param_names);
1457 format!("{} if {} else {}", body, test, orelse)
1458 }
1459 rustpython_ast::Expr::NamedExpr(named) => {
1460 let target = self.expr_to_string_with_placeholders(&*named.target, param_names);
1461 let value = self.expr_to_string_with_placeholders(&*named.value, param_names);
1462 format!("({} := {})", target, value)
1463 }
1464 rustpython_ast::Expr::Starred(starred) => {
1465 let value = self.expr_to_string_with_placeholders(&*starred.value, param_names);
1466 format!("*{}", value)
1467 }
1468 rustpython_ast::Expr::Await(await_expr) => {
1469 let value = self.expr_to_string_with_placeholders(&*await_expr.value, param_names);
1470 format!("await {}", value)
1471 }
1472 _ => "".to_string(),
1473 }
1474 }
1475
1476 fn transform_replacement_ast_to_expr(
1478 &self,
1479 replacement_ast: &rustpython_ast::Expr,
1480 obj_expr: &ast::Expr,
1481 ) -> ast::Expr {
1482 self.transform_expr_replacing_self(replacement_ast, obj_expr)
1484 }
1485
1486 fn transform_replacement_ast_with_params(
1488 &self,
1489 replacement_ast: &rustpython_ast::Expr,
1490 param_map: &HashMap<String, &ast::Expr>,
1491 ) -> ast::Expr {
1492 self.transform_expr_with_params(replacement_ast, param_map)
1493 }
1494
1495 fn transform_expr_with_params(
1497 &self,
1498 expr: &rustpython_ast::Expr,
1499 param_map: &HashMap<String, &ast::Expr>,
1500 ) -> ast::Expr {
1501 match expr {
1502 ast::Expr::Name(name) => {
1503 if let Some(replacement_expr) = param_map.get(name.id.as_str()) {
1505 (*replacement_expr).clone()
1506 } else {
1507 ast::Expr::Name(ast::ExprName {
1508 id: name.id.clone(),
1509 ctx: ast::ExprContext::Load,
1510 range: Default::default(),
1511 })
1512 }
1513 }
1514 ast::Expr::Attribute(attr) => {
1515 let value = self.transform_expr_with_params(&attr.value, param_map);
1517 ast::Expr::Attribute(ast::ExprAttribute {
1518 value: Box::new(value),
1519 attr: attr.attr.clone(),
1520 ctx: ast::ExprContext::Load,
1521 range: Default::default(),
1522 })
1523 }
1524 ast::Expr::Call(call) => {
1525 let func = self.transform_expr_with_params(&call.func, param_map);
1527 let args: Vec<ast::Expr> = call
1528 .args
1529 .iter()
1530 .map(|arg| self.transform_expr_with_params(arg, param_map))
1531 .collect();
1532 let keywords: Vec<ast::Keyword> = call
1533 .keywords
1534 .iter()
1535 .map(|kw| ast::Keyword {
1536 arg: kw.arg.clone(),
1537 value: self.transform_expr_with_params(&kw.value, param_map),
1538 range: Default::default(),
1539 })
1540 .collect();
1541
1542 ast::Expr::Call(ast::ExprCall {
1543 func: Box::new(func),
1544 args,
1545 keywords,
1546 range: Default::default(),
1547 })
1548 }
1549 ast::Expr::BinOp(binop) => {
1550 let left = self.transform_expr_with_params(&binop.left, param_map);
1551 let right = self.transform_expr_with_params(&binop.right, param_map);
1552 ast::Expr::BinOp(ast::ExprBinOp {
1553 left: Box::new(left),
1554 op: binop.op,
1555 right: Box::new(right),
1556 range: Default::default(),
1557 })
1558 }
1559 ast::Expr::Await(await_expr) => {
1560 let value = self.transform_expr_with_params(&await_expr.value, param_map);
1561 ast::Expr::Await(ast::ExprAwait {
1562 value: Box::new(value),
1563 range: Default::default(),
1564 })
1565 }
1566 _ => self.convert_expr(expr),
1568 }
1569 }
1570
1571 fn transform_expr_replacing_self(
1573 &self,
1574 expr: &rustpython_ast::Expr,
1575 obj_expr: &ast::Expr,
1576 ) -> ast::Expr {
1577 match expr {
1578 ast::Expr::Name(name) if name.id.as_str() == "self" => {
1579 obj_expr.clone()
1581 }
1582 ast::Expr::Attribute(attr) => {
1583 let value = self.transform_expr_replacing_self(&attr.value, obj_expr);
1585 ast::Expr::Attribute(ast::ExprAttribute {
1586 value: Box::new(value),
1587 attr: attr.attr.clone(),
1588 ctx: ast::ExprContext::Load,
1589 range: Default::default(),
1590 })
1591 }
1592 ast::Expr::Call(call) => {
1593 let func = self.transform_expr_replacing_self(&call.func, obj_expr);
1595 let args: Vec<ast::Expr> = call
1596 .args
1597 .iter()
1598 .map(|arg| self.transform_expr_replacing_self(arg, obj_expr))
1599 .collect();
1600 let keywords: Vec<ast::Keyword> = call
1601 .keywords
1602 .iter()
1603 .map(|kw| ast::Keyword {
1604 arg: kw.arg.clone(),
1605 value: self.transform_expr_replacing_self(&kw.value, obj_expr),
1606 range: Default::default(),
1607 })
1608 .collect();
1609
1610 ast::Expr::Call(ast::ExprCall {
1611 func: Box::new(func),
1612 args,
1613 keywords,
1614 range: Default::default(),
1615 })
1616 }
1617 _ => {
1619 self.convert_expr(expr)
1622 }
1623 }
1624 }
1625
1626 fn convert_expr(&self, expr: &rustpython_ast::Expr) -> ast::Expr {
1628 match expr {
1629 ast::Expr::Name(name) => ast::Expr::Name(ast::ExprName {
1630 id: name.id.clone(),
1631 ctx: ast::ExprContext::Load,
1632 range: Default::default(),
1633 }),
1634 ast::Expr::Constant(c) => ast::Expr::Constant(ast::ExprConstant {
1635 value: c.value.clone(),
1636 kind: c.kind.clone(),
1637 range: Default::default(),
1638 }),
1639 _ => {
1640 let expr_str = self.expr_to_string_with_placeholders(expr, &HashSet::new());
1642 match rustpython_parser::parse(
1643 &expr_str,
1644 rustpython_parser::Mode::Expression,
1645 "<conversion>",
1646 ) {
1647 Ok(ast::Mod::Expression(expr_mod)) => *expr_mod.body,
1648 _ => ast::Expr::Name(ast::ExprName {
1649 id: "ERROR".to_string().into(),
1650 ctx: ast::ExprContext::Load,
1651 range: Default::default(),
1652 }),
1653 }
1654 }
1655 }
1656 }
1657
1658 fn transform_replacement_ast_with_args(
1660 &self,
1661 ast: &rustpython_ast::Expr,
1662 param_map: &HashMap<String, &ast::Expr>,
1663 all_args: &[ast::Expr],
1664 all_keywords: &[ast::Keyword],
1665 parameters: &[ParameterInfo],
1666 ) -> String {
1667 self.rustpython_expr_to_string_with_args(ast, param_map, all_args, all_keywords, parameters)
1669 }
1670
1671 fn rustpython_expr_to_string_with_args(
1673 &self,
1674 expr: &rustpython_ast::Expr,
1675 param_map: &HashMap<String, &ast::Expr>,
1676 all_args: &[ast::Expr],
1677 all_keywords: &[ast::Keyword],
1678 parameters: &[ParameterInfo],
1679 ) -> String {
1680 self.rustpython_expr_to_string_with_args_impl(
1681 expr,
1682 param_map,
1683 all_args,
1684 all_keywords,
1685 parameters,
1686 true,
1687 )
1688 }
1689
1690 fn walk_replacement_ast(
1692 &self,
1693 expr: &rustpython_ast::Expr,
1694 call_args: &[ast::Expr],
1695 call_keywords: &[ast::Keyword],
1696 parameters: &[ParameterInfo],
1697 source_module: Option<&str>,
1698 ) -> String {
1699 self.walk_replacement_ast_impl(expr, call_args, call_keywords, parameters, source_module)
1700 }
1701
1702 fn walk_replacement_ast_impl(
1703 &self,
1704 expr: &rustpython_ast::Expr,
1705 call_args: &[ast::Expr],
1706 call_keywords: &[ast::Keyword],
1707 parameters: &[ParameterInfo],
1708 source_module: Option<&str>,
1709 ) -> String {
1710 use rustpython_ast as rpy_ast;
1711
1712 match expr {
1713 rpy_ast::Expr::Name(name) => {
1714 let name_str = name.id.as_str();
1715 if let Some(_param) = parameters
1718 .iter()
1719 .find(|p| p.name == name_str && p.is_vararg)
1720 {
1721 return name_str.to_string();
1724 }
1725
1726 if let Some(param_index) = parameters
1728 .iter()
1729 .position(|p| p.name == name_str && !p.is_vararg && !p.is_kwarg)
1730 {
1731 if param_index < call_args.len() {
1732 return self.expr_to_string(&call_args[param_index]);
1733 }
1734
1735 if param_index == call_args.len() && !call_keywords.is_empty() {
1739 for keyword in call_keywords.iter() {
1741 if keyword.arg.is_none() {
1742 return format!("**{}", self.expr_to_string(&keyword.value));
1744 }
1745 }
1746 }
1747 }
1748
1749 if let Some(_param) = parameters.iter().find(|p| p.is_kwarg && p.name == name_str) {
1751 let individual_kwargs: Vec<String> = call_keywords
1753 .iter()
1754 .map(|kw| {
1755 if let Some(arg) = &kw.arg {
1756 format!("{}={}", arg, self.expr_to_string(&kw.value))
1757 } else {
1758 format!("**{}", self.expr_to_string(&kw.value))
1760 }
1761 })
1762 .collect();
1763
1764 if !individual_kwargs.is_empty() {
1765 return individual_kwargs.join(", ");
1766 } else {
1767 return String::new();
1769 }
1770 }
1771
1772 if let Some(keyword) = call_keywords
1774 .iter()
1775 .find(|kw| kw.arg.as_ref().map(|arg| arg.as_str()) == Some(name_str))
1776 {
1777 return self.expr_to_string(&keyword.value);
1778 }
1779
1780 if let Some(param) = parameters.iter().find(|p| p.name == name_str) {
1782 if let Some(param_index) = parameters.iter().position(|p| p.name == name_str) {
1783 let is_provided = param_index < call_args.len()
1784 || call_keywords.iter().any(|kw| {
1785 kw.arg.as_ref().map(|arg| arg.as_str()) == Some(name_str)
1786 });
1787
1788 if !is_provided && param.has_default {
1789 return "__OMIT_PARAMETER__".to_string();
1791 }
1792 }
1793 }
1794
1795 if let Some(source_mod) = source_module {
1797 let full_name = format!("{}.{}", source_mod, name_str);
1800 if self.import_map.values().any(|v| v == &full_name) {
1801 name_str.to_string()
1803 } else {
1804 full_name
1806 }
1807 } else {
1808 name_str.to_string()
1810 }
1811 }
1812
1813 rpy_ast::Expr::Attribute(attr) => {
1814 let value = self.walk_replacement_ast_impl(
1815 &attr.value,
1816 call_args,
1817 call_keywords,
1818 parameters,
1819 source_module,
1820 );
1821 format!("{}.{}", value, attr.attr)
1822 }
1823
1824 rpy_ast::Expr::Call(call) => {
1825 let func = match call.func.as_ref() {
1827 rpy_ast::Expr::Name(name) => {
1828 let func_name = name.id.as_str();
1829 if let Some(param_index) =
1831 parameters.iter().position(|p| p.name == func_name)
1832 {
1833 if param_index < call_args.len() {
1834 self.expr_to_string(&call_args[param_index])
1835 } else {
1836 if !self.import_map.contains_key(func_name)
1838 && !self.builtins.contains(func_name)
1839 {
1840 format!("{}.{}", self.module_name, func_name)
1841 } else {
1842 func_name.to_string()
1843 }
1844 }
1845 } else {
1846 if self.import_map.contains_key(func_name)
1851 || self.builtins.contains(func_name)
1852 {
1853 func_name.to_string()
1855 } else if self.module_functions.contains(func_name) {
1856 func_name.to_string()
1858 } else if let Some(source_mod) = source_module {
1859 let full_name = format!("{}.{}", source_mod, func_name);
1862 if self.import_map.values().any(|v| v == &full_name) {
1863 func_name.to_string()
1865 } else {
1866 full_name
1868 }
1869 } else {
1870 func_name.to_string()
1872 }
1873 }
1874 }
1875 _ => self.walk_replacement_ast_impl(
1876 &call.func,
1877 call_args,
1878 call_keywords,
1879 parameters,
1880 source_module,
1881 ),
1882 };
1883 let args: Vec<String> = call
1884 .args
1885 .iter()
1886 .filter_map(|arg| {
1887 let value = self.walk_replacement_ast_impl(
1888 arg,
1889 call_args,
1890 call_keywords,
1891 parameters,
1892 source_module,
1893 );
1894
1895 if value.is_empty() {
1897 return None;
1898 }
1899
1900 if let rpy_ast::Expr::Name(name) = arg {
1903 let name_str = name.id.as_str();
1904 if value == name_str && parameters.iter().any(|p| p.name == name_str) {
1909 if let Some(param_index) =
1911 parameters.iter().position(|p| p.name == name_str)
1912 {
1913 let param = ¶meters[param_index];
1914 let is_provided = param_index < call_args.len()
1915 || call_keywords.iter().any(|kw| {
1916 kw.arg
1917 .as_ref()
1918 .is_some_and(|arg| arg.as_str() == name_str)
1919 });
1920
1921 if !is_provided {
1922 if param.has_default {
1924 return None;
1926 } else {
1927 }
1930 }
1931 }
1932 }
1933 }
1934
1935 if value == "__OMIT_PARAMETER__" {
1937 return None;
1938 }
1939
1940 Some(value)
1941 })
1942 .collect();
1943 let keywords: Vec<String> = call
1944 .keywords
1945 .iter()
1946 .filter_map(|kw| {
1947 let value = self.walk_replacement_ast_impl(
1948 &kw.value,
1949 call_args,
1950 call_keywords,
1951 parameters,
1952 source_module,
1953 );
1954 if let Some(arg) = &kw.arg {
1955 if value == "__OMIT_PARAMETER__" {
1957 return None;
1958 }
1959
1960 let is_unsubstituted = value == arg.as_str()
1962 || (source_module.is_some()
1963 && value
1964 == format!("{}.{}", source_module.unwrap(), arg.as_str()));
1965
1966 if is_unsubstituted {
1967 if let Some(param_idx) =
1969 parameters.iter().position(|p| p.name == arg.as_str())
1970 {
1971 let param = ¶meters[param_idx];
1972 let is_provided = param_idx < call_args.len()
1973 || call_keywords.iter().any(|kw| {
1974 kw.arg
1975 .as_ref()
1976 .is_some_and(|a| a.as_str() == arg.as_str())
1977 });
1978
1979 if is_provided {
1980 return Some(format!("{}={}", arg, value));
1982 } else if param.has_default {
1983 return None;
1985 } else {
1986 return Some(format!("{}={}", arg, value));
1989 }
1990 }
1991 return None;
1993 }
1994 Some(format!("{}={}", arg, value))
1995 } else {
1996 if value.is_empty() {
2000 return None;
2001 }
2002
2003 if value.contains("=") {
2005 Some(value)
2007 } else if value.starts_with("**") {
2008 Some(value)
2010 } else {
2011 Some(format!("**{}", value))
2013 }
2014 }
2015 })
2016 .collect();
2017
2018 let mut all_args = args;
2019 all_args.extend(keywords);
2020 format!("{}({})", func, all_args.join(", "))
2021 }
2022
2023 rpy_ast::Expr::Constant(constant) => {
2024 self.format_constant(&constant.value)
2026 }
2027
2028 rpy_ast::Expr::IfExp(if_exp) => {
2030 let body = self.walk_replacement_ast_impl(
2031 &if_exp.body,
2032 call_args,
2033 call_keywords,
2034 parameters,
2035 source_module,
2036 );
2037 let test = self.walk_replacement_ast_impl(
2038 &if_exp.test,
2039 call_args,
2040 call_keywords,
2041 parameters,
2042 source_module,
2043 );
2044 let orelse = self.walk_replacement_ast_impl(
2045 &if_exp.orelse,
2046 call_args,
2047 call_keywords,
2048 parameters,
2049 source_module,
2050 );
2051 format!("{} if {} else {}", body, test, orelse)
2052 }
2053
2054 rpy_ast::Expr::BoolOp(boolop) => {
2055 let values: Vec<String> = boolop
2056 .values
2057 .iter()
2058 .map(|v| {
2059 self.walk_replacement_ast_impl(
2060 v,
2061 call_args,
2062 call_keywords,
2063 parameters,
2064 source_module,
2065 )
2066 })
2067 .collect();
2068 let op = match &boolop.op {
2069 rustpython_ast::BoolOp::And => " and ",
2070 rustpython_ast::BoolOp::Or => " or ",
2071 };
2072 values.join(op)
2073 }
2074
2075 rpy_ast::Expr::BinOp(binop) => {
2076 let left = self.walk_replacement_ast_impl(
2077 &binop.left,
2078 call_args,
2079 call_keywords,
2080 parameters,
2081 source_module,
2082 );
2083 let right = self.walk_replacement_ast_impl(
2084 &binop.right,
2085 call_args,
2086 call_keywords,
2087 parameters,
2088 source_module,
2089 );
2090 let op = match &binop.op {
2091 rustpython_ast::Operator::Add => "+",
2092 rustpython_ast::Operator::Sub => "-",
2093 rustpython_ast::Operator::Mult => "*",
2094 rustpython_ast::Operator::Div => "/",
2095 rustpython_ast::Operator::Mod => "%",
2096 rustpython_ast::Operator::Pow => "**",
2097 rustpython_ast::Operator::FloorDiv => "//",
2098 rustpython_ast::Operator::LShift => "<<",
2099 rustpython_ast::Operator::RShift => ">>",
2100 rustpython_ast::Operator::BitOr => "|",
2101 rustpython_ast::Operator::BitXor => "^",
2102 rustpython_ast::Operator::BitAnd => "&",
2103 rustpython_ast::Operator::MatMult => "@",
2104 };
2105 format!("{} {} {}", left, op, right)
2106 }
2107
2108 rpy_ast::Expr::Subscript(sub) => {
2109 let value = self.walk_replacement_ast_impl(
2110 &sub.value,
2111 call_args,
2112 call_keywords,
2113 parameters,
2114 source_module,
2115 );
2116 let slice = self.walk_replacement_ast_impl(
2117 &sub.slice,
2118 call_args,
2119 call_keywords,
2120 parameters,
2121 source_module,
2122 );
2123 format!("{}[{}]", value, slice)
2124 }
2125
2126 rpy_ast::Expr::Compare(compare) => {
2127 let mut result = self.walk_replacement_ast_impl(
2128 &compare.left,
2129 call_args,
2130 call_keywords,
2131 parameters,
2132 source_module,
2133 );
2134 for (op, comparator) in compare.ops.iter().zip(&compare.comparators) {
2135 let op_str = match op {
2136 rustpython_ast::CmpOp::Eq => " == ",
2137 rustpython_ast::CmpOp::NotEq => " != ",
2138 rustpython_ast::CmpOp::Lt => " < ",
2139 rustpython_ast::CmpOp::LtE => " <= ",
2140 rustpython_ast::CmpOp::Gt => " > ",
2141 rustpython_ast::CmpOp::GtE => " >= ",
2142 rustpython_ast::CmpOp::Is => " is ",
2143 rustpython_ast::CmpOp::IsNot => " is not ",
2144 rustpython_ast::CmpOp::In => " in ",
2145 rustpython_ast::CmpOp::NotIn => " not in ",
2146 };
2147 result.push_str(op_str);
2148 result.push_str(&self.walk_replacement_ast_impl(
2149 comparator,
2150 call_args,
2151 call_keywords,
2152 parameters,
2153 source_module,
2154 ));
2155 }
2156 result
2157 }
2158
2159 rpy_ast::Expr::UnaryOp(unaryop) => {
2160 let operand = self.walk_replacement_ast_impl(
2161 &unaryop.operand,
2162 call_args,
2163 call_keywords,
2164 parameters,
2165 source_module,
2166 );
2167 let op = match &unaryop.op {
2168 rustpython_ast::UnaryOp::Not => "not ",
2169 rustpython_ast::UnaryOp::UAdd => "+",
2170 rustpython_ast::UnaryOp::USub => "-",
2171 rustpython_ast::UnaryOp::Invert => "~",
2172 };
2173 format!("{}{}", op, operand)
2174 }
2175
2176 rpy_ast::Expr::List(list) => {
2177 let items: Vec<String> = list
2178 .elts
2179 .iter()
2180 .map(|e| {
2181 self.walk_replacement_ast_impl(
2182 e,
2183 call_args,
2184 call_keywords,
2185 parameters,
2186 source_module,
2187 )
2188 })
2189 .collect();
2190 format!("[{}]", items.join(", "))
2191 }
2192
2193 rpy_ast::Expr::Tuple(tuple) => {
2194 let items: Vec<String> = tuple
2195 .elts
2196 .iter()
2197 .map(|e| {
2198 self.walk_replacement_ast_impl(
2199 e,
2200 call_args,
2201 call_keywords,
2202 parameters,
2203 source_module,
2204 )
2205 })
2206 .collect();
2207 if items.len() == 1 {
2208 format!("({},)", items[0])
2209 } else {
2210 format!("({})", items.join(", "))
2211 }
2212 }
2213
2214 rpy_ast::Expr::Dict(dict) => {
2215 let mut items = Vec::new();
2216 for (key_opt, value) in dict.keys.iter().zip(&dict.values) {
2217 if let Some(key) = key_opt {
2218 let key_str = self.walk_replacement_ast_impl(
2219 key,
2220 call_args,
2221 call_keywords,
2222 parameters,
2223 source_module,
2224 );
2225 let value_str = self.walk_replacement_ast_impl(
2226 value,
2227 call_args,
2228 call_keywords,
2229 parameters,
2230 source_module,
2231 );
2232 items.push(format!("{}: {}", key_str, value_str));
2233 } else {
2234 let value_str = self.walk_replacement_ast_impl(
2236 value,
2237 call_args,
2238 call_keywords,
2239 parameters,
2240 source_module,
2241 );
2242 items.push(format!("**{}", value_str));
2243 }
2244 }
2245 format!("{{{}}}", items.join(", "))
2246 }
2247
2248 rpy_ast::Expr::Set(set) => {
2249 let items: Vec<String> = set
2250 .elts
2251 .iter()
2252 .map(|e| {
2253 self.walk_replacement_ast_impl(
2254 e,
2255 call_args,
2256 call_keywords,
2257 parameters,
2258 source_module,
2259 )
2260 })
2261 .collect();
2262 format!("{{{}}}", items.join(", "))
2263 }
2264
2265 rpy_ast::Expr::NamedExpr(named) => {
2266 let target = self.walk_replacement_ast_impl(
2267 &named.target,
2268 call_args,
2269 call_keywords,
2270 parameters,
2271 source_module,
2272 );
2273 let value = self.walk_replacement_ast_impl(
2274 &named.value,
2275 call_args,
2276 call_keywords,
2277 parameters,
2278 source_module,
2279 );
2280 format!("({} := {})", target, value)
2281 }
2282
2283 rpy_ast::Expr::Starred(starred) => {
2284 if let rpy_ast::Expr::Name(name) = &*starred.value {
2286 if let Some(_param) = parameters
2287 .iter()
2288 .find(|p| p.is_vararg && p.name == name.id.as_str())
2289 {
2290 let regular_param_count = parameters
2292 .iter()
2293 .filter(|p| !p.is_vararg && !p.is_kwarg)
2294 .count();
2295
2296 if regular_param_count == 0 {
2298 let args_strs: Vec<String> = call_args
2299 .iter()
2300 .map(|arg| self.expr_to_string(arg))
2301 .collect();
2302 return args_strs.join(", ");
2303 }
2304
2305 if call_args.len() > regular_param_count {
2307 let extra_args: Vec<String> = call_args[regular_param_count..]
2308 .iter()
2309 .map(|arg| self.expr_to_string(arg))
2310 .collect();
2311 return extra_args.join(", ");
2312 }
2313 return String::new(); }
2315 }
2316
2317 let value = self.walk_replacement_ast_impl(
2319 &starred.value,
2320 call_args,
2321 call_keywords,
2322 parameters,
2323 source_module,
2324 );
2325 format!("*{}", value)
2326 }
2327
2328 rpy_ast::Expr::Await(await_expr) => {
2329 let value = self.walk_replacement_ast_impl(
2330 &await_expr.value,
2331 call_args,
2332 call_keywords,
2333 parameters,
2334 source_module,
2335 );
2336 format!("await {}", value)
2337 }
2338
2339 rpy_ast::Expr::Slice(slice) => {
2340 let lower = slice
2341 .lower
2342 .as_ref()
2343 .map(|e| {
2344 self.walk_replacement_ast_impl(
2345 e,
2346 call_args,
2347 call_keywords,
2348 parameters,
2349 source_module,
2350 )
2351 })
2352 .unwrap_or_default();
2353 let upper = slice
2354 .upper
2355 .as_ref()
2356 .map(|e| {
2357 self.walk_replacement_ast_impl(
2358 e,
2359 call_args,
2360 call_keywords,
2361 parameters,
2362 source_module,
2363 )
2364 })
2365 .unwrap_or_default();
2366 if let Some(step) = &slice.step {
2367 let step_str = self.walk_replacement_ast_impl(
2368 step,
2369 call_args,
2370 call_keywords,
2371 parameters,
2372 source_module,
2373 );
2374 format!("{}:{}:{}", lower, upper, step_str)
2375 } else {
2376 format!("{}:{}", lower, upper)
2377 }
2378 }
2379
2380 _ => {
2383 self.expr_to_string_with_placeholders(expr, &HashSet::new())
2385 }
2386 }
2387 }
2388
2389 #[allow(clippy::only_used_in_recursion)] fn format_constant(&self, constant: &rustpython_ast::Constant) -> String {
2391 use rustpython_ast::Constant;
2392 match constant {
2393 Constant::Str(s) => format!("\"{}\"", s.replace("\\", "\\\\").replace("\"", "\\\"")),
2394 Constant::Bytes(b) => format!("b\"{}\"", String::from_utf8_lossy(b)),
2395 Constant::Int(i) => i.to_string(),
2396 Constant::Float(f) => f.to_string(),
2397 Constant::Complex { real, imag } => {
2398 if *real == 0.0 {
2399 format!("{}j", imag)
2400 } else if *imag == 0.0 {
2401 format!("{}", real)
2402 } else if *imag < 0.0 {
2403 format!("{}{}j", real, imag)
2404 } else {
2405 format!("{}+{}j", real, imag)
2406 }
2407 }
2408 Constant::Bool(b) => {
2409 if *b {
2410 "True".to_string()
2411 } else {
2412 "False".to_string()
2413 }
2414 }
2415 Constant::None => "None".to_string(),
2416 Constant::Ellipsis => "...".to_string(),
2417 Constant::Tuple(items) => {
2418 let elements: Vec<String> = items
2419 .iter()
2420 .map(|item| self.format_constant(item))
2421 .collect();
2422 format!("({})", elements.join(", "))
2423 }
2424 }
2425 }
2426
2427 fn rustpython_expr_to_string_for_template(
2428 &self,
2429 expr: &rustpython_ast::Expr,
2430 param_map: &HashMap<String, &ast::Expr>,
2431 all_args: &[ast::Expr],
2432 all_keywords: &[ast::Keyword],
2433 parameters: &[ParameterInfo],
2434 ) -> String {
2435 self.rustpython_expr_to_string_with_args_impl(
2436 expr,
2437 param_map,
2438 all_args,
2439 all_keywords,
2440 parameters,
2441 false,
2442 )
2443 }
2444
2445 fn rustpython_expr_to_string_with_args_impl(
2446 &self,
2447 expr: &rustpython_ast::Expr,
2448 param_map: &HashMap<String, &ast::Expr>,
2449 all_args: &[ast::Expr],
2450 all_keywords: &[ast::Keyword],
2451 parameters: &[ParameterInfo],
2452 apply_name_resolution: bool,
2453 ) -> String {
2454 use rustpython_ast as rpy_ast;
2455
2456 match expr {
2457 rpy_ast::Expr::Name(name) => {
2458 let name_str = name.id.as_str();
2459
2460 if let Some(arg_expr) = param_map.get(name_str) {
2462 return self.expr_to_string(arg_expr);
2463 }
2464
2465 if parameters.iter().any(|p| p.is_vararg && p.name == name_str) {
2467 return name_str.to_string();
2468 }
2469
2470 if parameters.iter().any(|p| p.is_kwarg && p.name == name_str) {
2472 return name_str.to_string();
2473 }
2474
2475 if !param_map.is_empty()
2480 && parameters
2481 .iter()
2482 .any(|p| p.name == name_str && !p.is_vararg && !p.is_kwarg)
2483 {
2484 return String::new();
2486 }
2487
2488 name_str.to_string()
2490 }
2491 rpy_ast::Expr::Call(call) => {
2492 let func_str = match &*call.func {
2494 rpy_ast::Expr::Name(name) => {
2495 let func_name = name.id.as_str();
2496 if let Some(param_expr) = param_map.get(func_name) {
2498 self.expr_to_string(param_expr)
2499 } else if let Some(full_name) = self.import_map.get(func_name) {
2500 full_name.clone()
2502 } else {
2503 if apply_name_resolution && !self.builtins.contains(func_name) {
2505 format!("{}.{}", self.module_name, func_name)
2507 } else {
2508 func_name.to_string()
2509 }
2510 }
2511 }
2512 _ => {
2513 self.rustpython_expr_to_string_with_args_impl(
2515 &call.func,
2516 param_map,
2517 all_args,
2518 all_keywords,
2519 parameters,
2520 apply_name_resolution,
2521 )
2522 }
2523 };
2524
2525 let mut args = Vec::new();
2526 for arg in &call.args {
2527 let arg_str = self.rustpython_expr_to_string_with_args(
2528 arg,
2529 param_map,
2530 all_args,
2531 all_keywords,
2532 parameters,
2533 );
2534 if !arg_str.is_empty() {
2535 args.push(arg_str);
2536 }
2537 }
2538
2539 for keyword in &call.keywords {
2541 if let Some(arg_name) = &keyword.arg {
2542 let value = self.rustpython_expr_to_string_with_args(
2543 &keyword.value,
2544 param_map,
2545 all_args,
2546 all_keywords,
2547 parameters,
2548 );
2549 if !value.is_empty() && value != arg_name.as_str() {
2551 args.push(format!("{}={}", arg_name, value));
2552 }
2553 } else {
2554 if let rpy_ast::Expr::Name(name) = &keyword.value {
2556 if parameters
2557 .iter()
2558 .any(|p| p.is_kwarg && p.name == name.id.as_str())
2559 {
2560 let has_dict_unpack =
2562 all_keywords.iter().any(|kw| kw.arg.is_none());
2563
2564 if has_dict_unpack {
2565 for kw in all_keywords {
2567 if kw.arg.is_none() {
2568 args.push(format!(
2570 "**{}",
2571 self.expr_to_string(&kw.value)
2572 ));
2573 } else {
2574 args.push(format!(
2576 "{}={}",
2577 kw.arg.as_ref().unwrap(),
2578 self.expr_to_string(&kw.value)
2579 ));
2580 }
2581 }
2582 } else {
2583 for kw in all_keywords {
2585 if let Some(arg_name) = &kw.arg {
2586 args.push(format!(
2587 "{}={}",
2588 arg_name,
2589 self.expr_to_string(&kw.value)
2590 ));
2591 }
2592 }
2593 }
2594 continue;
2595 }
2596 }
2597 let value = self.rustpython_expr_to_string_with_args(
2599 &keyword.value,
2600 param_map,
2601 all_args,
2602 all_keywords,
2603 parameters,
2604 );
2605 if !value.is_empty() {
2606 args.push(format!("**{}", value));
2607 }
2608 }
2609 }
2610
2611 let has_dict_unpack = all_keywords.iter().any(|kw| kw.arg.is_none());
2613 let has_kwarg_param = parameters.iter().any(|p| p.is_kwarg);
2614
2615 if has_dict_unpack && !has_kwarg_param {
2616 for kw in all_keywords {
2618 if kw.arg.is_none() {
2619 args.push(format!("**{}", self.expr_to_string(&kw.value)));
2620 }
2621 }
2622 }
2623
2624 format!("{}({})", func_str, args.join(", "))
2625 }
2626 rpy_ast::Expr::Attribute(attr) => {
2627 let value = self.rustpython_expr_to_string_with_args(
2628 &attr.value,
2629 param_map,
2630 all_args,
2631 all_keywords,
2632 parameters,
2633 );
2634 format!("{}.{}", value, attr.attr)
2635 }
2636 rpy_ast::Expr::BinOp(binop) => {
2637 let left = self.rustpython_expr_to_string_with_args(
2638 &binop.left,
2639 param_map,
2640 all_args,
2641 all_keywords,
2642 parameters,
2643 );
2644 let right = self.rustpython_expr_to_string_with_args(
2645 &binop.right,
2646 param_map,
2647 all_args,
2648 all_keywords,
2649 parameters,
2650 );
2651 let op = match &binop.op {
2652 rpy_ast::Operator::Add => "+",
2653 rpy_ast::Operator::Sub => "-",
2654 rpy_ast::Operator::Mult => "*",
2655 rpy_ast::Operator::Div => "/",
2656 rpy_ast::Operator::FloorDiv => "//",
2657 rpy_ast::Operator::Mod => "%",
2658 rpy_ast::Operator::Pow => "**",
2659 rpy_ast::Operator::LShift => "<<",
2660 rpy_ast::Operator::RShift => ">>",
2661 rpy_ast::Operator::BitOr => "|",
2662 rpy_ast::Operator::BitXor => "^",
2663 rpy_ast::Operator::BitAnd => "&",
2664 rpy_ast::Operator::MatMult => "@",
2665 };
2666 format!("{} {} {}", left, op, right)
2667 }
2668 rpy_ast::Expr::Starred(starred) => {
2669 if let rpy_ast::Expr::Name(name) = &*starred.value {
2671 if let Some(_param) = parameters
2672 .iter()
2673 .find(|p| p.is_vararg && p.name == name.id.as_str())
2674 {
2675 let regular_param_count = parameters
2677 .iter()
2678 .filter(|p| !p.is_vararg && !p.is_kwarg)
2679 .count();
2680
2681 if regular_param_count == 0 {
2683 let args_strs: Vec<String> = all_args
2684 .iter()
2685 .map(|arg| self.expr_to_string(arg))
2686 .collect();
2687 return args_strs.join(", ");
2688 }
2689
2690 if all_args.len() > regular_param_count {
2692 let extra_args: Vec<String> = all_args[regular_param_count..]
2693 .iter()
2694 .map(|arg| self.expr_to_string(arg))
2695 .collect();
2696 return extra_args.join(", ");
2697 }
2698 return String::new(); }
2700 }
2701
2702 let value = self.rustpython_expr_to_string_with_args(
2704 &starred.value,
2705 param_map,
2706 all_args,
2707 all_keywords,
2708 parameters,
2709 );
2710 format!("*{}", value)
2711 }
2712 rpy_ast::Expr::Dict(dict) if dict.keys.is_empty() && dict.values.len() == 1 => {
2714 if let rpy_ast::Expr::Name(name) = &dict.values[0] {
2716 if let Some(_param) = parameters
2717 .iter()
2718 .find(|p| p.is_kwarg && p.name == name.id.as_str())
2719 {
2720 let kwargs: Vec<String> = all_keywords
2722 .iter()
2723 .map(|kw| {
2724 if let Some(arg_name) = &kw.arg {
2725 format!("{}={}", arg_name, self.expr_to_string(&kw.value))
2726 } else {
2727 format!("**{}", self.expr_to_string(&kw.value))
2728 }
2729 })
2730 .collect();
2731 return kwargs.join(", ");
2732 }
2733 }
2734 let param_names: HashSet<String> = param_map.keys().cloned().collect();
2736 self.expr_to_string_with_placeholders(expr, ¶m_names)
2737 }
2738 rpy_ast::Expr::Await(await_expr) => {
2739 let value = self.rustpython_expr_to_string_with_args(
2741 &await_expr.value,
2742 param_map,
2743 all_args,
2744 all_keywords,
2745 parameters,
2746 );
2747 format!("await {}", value)
2748 }
2749 rpy_ast::Expr::Subscript(subscript) => {
2750 let value = self.rustpython_expr_to_string_with_args(
2752 &subscript.value,
2753 param_map,
2754 all_args,
2755 all_keywords,
2756 parameters,
2757 );
2758 let slice = self.rustpython_expr_to_string_with_args(
2759 &subscript.slice,
2760 param_map,
2761 all_args,
2762 all_keywords,
2763 parameters,
2764 );
2765 format!("{}[{}]", value, slice)
2766 }
2767 rpy_ast::Expr::IfExp(if_exp) => {
2768 let body = self.rustpython_expr_to_string_with_args(
2770 &if_exp.body,
2771 param_map,
2772 all_args,
2773 all_keywords,
2774 parameters,
2775 );
2776 let test = self.rustpython_expr_to_string_with_args(
2777 &if_exp.test,
2778 param_map,
2779 all_args,
2780 all_keywords,
2781 parameters,
2782 );
2783 let orelse = self.rustpython_expr_to_string_with_args(
2784 &if_exp.orelse,
2785 param_map,
2786 all_args,
2787 all_keywords,
2788 parameters,
2789 );
2790 format!("{} if {} else {}", body, test, orelse)
2791 }
2792 rpy_ast::Expr::UnaryOp(unary) => {
2793 let operand = self.rustpython_expr_to_string_with_args(
2795 &unary.operand,
2796 param_map,
2797 all_args,
2798 all_keywords,
2799 parameters,
2800 );
2801 let op = match &unary.op {
2802 rustpython_ast::UnaryOp::Not => "not ",
2803 rustpython_ast::UnaryOp::UAdd => "+",
2804 rustpython_ast::UnaryOp::USub => "-",
2805 rustpython_ast::UnaryOp::Invert => "~",
2806 };
2807 format!("{}{}", op, operand)
2808 }
2809 rpy_ast::Expr::Compare(compare) => {
2810 let mut result = self.rustpython_expr_to_string_with_args(
2812 &compare.left,
2813 param_map,
2814 all_args,
2815 all_keywords,
2816 parameters,
2817 );
2818 for (op, comparator) in compare.ops.iter().zip(&compare.comparators) {
2819 let op_str = match op {
2820 rustpython_ast::CmpOp::Eq => " == ",
2821 rustpython_ast::CmpOp::NotEq => " != ",
2822 rustpython_ast::CmpOp::Lt => " < ",
2823 rustpython_ast::CmpOp::LtE => " <= ",
2824 rustpython_ast::CmpOp::Gt => " > ",
2825 rustpython_ast::CmpOp::GtE => " >= ",
2826 rustpython_ast::CmpOp::Is => " is ",
2827 rustpython_ast::CmpOp::IsNot => " is not ",
2828 rustpython_ast::CmpOp::In => " in ",
2829 rustpython_ast::CmpOp::NotIn => " not in ",
2830 };
2831 result.push_str(op_str);
2832 result.push_str(&self.rustpython_expr_to_string_with_args(
2833 comparator,
2834 param_map,
2835 all_args,
2836 all_keywords,
2837 parameters,
2838 ));
2839 }
2840 result
2841 }
2842 rpy_ast::Expr::NamedExpr(named) => {
2843 let target = self.rustpython_expr_to_string_with_args(
2845 &named.target,
2846 param_map,
2847 all_args,
2848 all_keywords,
2849 parameters,
2850 );
2851 let value = self.rustpython_expr_to_string_with_args(
2852 &named.value,
2853 param_map,
2854 all_args,
2855 all_keywords,
2856 parameters,
2857 );
2858 format!("({} := {})", target, value)
2859 }
2860 rpy_ast::Expr::BoolOp(boolop) => {
2861 let values: Vec<String> = boolop
2863 .values
2864 .iter()
2865 .map(|v| {
2866 self.rustpython_expr_to_string_with_args(
2867 v,
2868 param_map,
2869 all_args,
2870 all_keywords,
2871 parameters,
2872 )
2873 })
2874 .collect();
2875 let op = match &boolop.op {
2876 rustpython_ast::BoolOp::And => " and ",
2877 rustpython_ast::BoolOp::Or => " or ",
2878 };
2879 values.join(op)
2880 }
2881 rpy_ast::Expr::Dict(dict) => {
2882 let mut items = Vec::new();
2884 for (key_opt, value) in dict.keys.iter().zip(&dict.values) {
2885 if let Some(key) = key_opt {
2886 let key_str = self.rustpython_expr_to_string_with_args(
2887 key,
2888 param_map,
2889 all_args,
2890 all_keywords,
2891 parameters,
2892 );
2893 let value_str = self.rustpython_expr_to_string_with_args(
2894 value,
2895 param_map,
2896 all_args,
2897 all_keywords,
2898 parameters,
2899 );
2900 items.push(format!("{}: {}", key_str, value_str));
2901 } else {
2902 let value_str = self.rustpython_expr_to_string_with_args(
2904 value,
2905 param_map,
2906 all_args,
2907 all_keywords,
2908 parameters,
2909 );
2910 items.push(format!("**{}", value_str));
2911 }
2912 }
2913 format!("{{{}}}", items.join(", "))
2914 }
2915 _ => {
2917 if let rpy_ast::Expr::Name(name) = expr {
2919 if let Some(_param) = parameters
2920 .iter()
2921 .find(|p| p.is_kwarg && p.name == name.id.as_str())
2922 {
2923 let kwargs: Vec<String> = all_keywords
2925 .iter()
2926 .map(|kw| {
2927 if let Some(arg_name) = &kw.arg {
2928 format!("{}={}", arg_name, self.expr_to_string(&kw.value))
2929 } else {
2930 format!("**{}", self.expr_to_string(&kw.value))
2931 }
2932 })
2933 .collect();
2934 if !kwargs.is_empty() {
2935 return kwargs.join(", ");
2936 }
2937 }
2938 }
2939
2940 let param_names: HashSet<String> = param_map.keys().cloned().collect();
2942 self.expr_to_string_with_placeholders(expr, ¶m_names)
2943 }
2944 }
2945 }
2946
2947 fn should_unwrap_magic_method_call(
2950 &self,
2951 replacement_ast: &rustpython_ast::Expr,
2952 builtin_name: &str,
2953 ) -> bool {
2954 use rustpython_ast::Expr;
2955
2956 tracing::debug!(
2957 "should_unwrap_magic_method_call: builtin_name={}",
2958 builtin_name
2959 );
2960
2961 if let Expr::Call(call) = replacement_ast {
2963 if let Expr::Name(func_name) = &*call.func {
2964 if func_name.id.as_str() == builtin_name && call.args.len() == 1 {
2965 tracing::debug!("Found matching builtin call, checking argument type");
2966 match &call.args[0] {
2968 Expr::Name(name) => {
2970 let should_unwrap = name.id.as_str() == "self"; tracing::debug!(
2972 "Name argument '{}', should_unwrap: {}",
2973 name.id,
2974 should_unwrap
2975 );
2976 should_unwrap
2977 }
2978
2979 Expr::Attribute(_) => {
2981 tracing::debug!(
2982 "Attribute argument - NOT unwrapping to preserve builtin call"
2983 );
2984 false
2985 }
2986
2987 Expr::Call(call_expr) => {
2989 if let Expr::Attribute(attr) = &*call_expr.func {
2991 if let Expr::Name(base_name) = &*attr.value {
2992 if base_name.id.as_str() == "self" {
2993 tracing::debug!("Method call on self - unwrapping");
2995 return true;
2996 }
2997 }
2998 }
2999 tracing::debug!("Call argument - NOT unwrapping");
3000 false
3001 }
3002
3003 _ => {
3005 tracing::debug!("Other expression type - NOT unwrapping");
3006 false
3007 }
3008 }
3009 } else {
3010 tracing::debug!("Not matching builtin call or wrong arg count");
3011 false
3012 }
3013 } else {
3014 tracing::debug!("Call func is not a simple name");
3015 false
3016 }
3017 } else {
3018 tracing::debug!("Replacement is not a call expression");
3019 false
3020 }
3021 }
3022}