1use anyhow::{anyhow, Result};
21use rustpython_ast::{ExceptHandler as AstExceptHandler, Expr as AstExpr, Ranged, Stmt as AstStmt};
22use rustpython_parser::{parse, Mode};
23use std::collections::HashMap;
24use std::ops::Range;
25
26use crate::core::{CollectorResult, ReplaceInfo};
27use crate::types::TypeIntrospectionMethod;
28
29pub struct PythonModule<'a> {
31 source: &'a str,
32 ast: Vec<AstStmt>,
33 position_map: HashMap<u32, (u32, u32)>,
35}
36
37impl<'a> PythonModule<'a> {
38 pub fn parse(source: &'a str) -> Result<Self> {
40 let parsed =
41 parse(source, Mode::Module, "<module>").map_err(|e| anyhow!("Parse error: {:?}", e))?;
42
43 let ast = match parsed {
45 rustpython_ast::Mod::Module(module) => module.body,
46 _ => return Err(anyhow!("Expected Module, got Expression")),
47 };
48
49 let position_map = Self::build_position_map(source);
51
52 Ok(Self {
53 source,
54 ast,
55 position_map,
56 })
57 }
58
59 fn build_position_map(source: &str) -> HashMap<u32, (u32, u32)> {
61 let mut map = HashMap::new();
62 let mut line = 1;
63 let mut col = 0;
64
65 for (offset, ch) in source.char_indices() {
66 map.insert(offset as u32, (line, col));
67 if ch == '\n' {
68 line += 1;
69 col = 0;
70 } else {
71 col += 1;
72 }
73 }
74
75 map.insert(source.len() as u32, (line, col));
77
78 map
79 }
80
81 pub fn ast(&self) -> &[AstStmt] {
83 &self.ast
84 }
85
86 pub fn offset_to_position(&self, offset: usize) -> Option<(u32, u32)> {
88 self.position_map.get(&(offset as u32)).copied()
89 }
90
91 pub fn line_col_at_offset(&self, offset: usize) -> (u32, u32) {
93 self.offset_to_position(offset).unwrap_or((1, 0))
94 }
95
96 pub fn text_at_range(&self, range: Range<usize>) -> &str {
98 &self.source[range]
99 }
100}
101
102pub fn collect_deprecated_functions(_source: &str, _module_name: &str) -> Result<CollectorResult> {
105 Ok(CollectorResult {
108 replacements: HashMap::new(),
109 unreplaceable: HashMap::new(),
110 imports: Vec::new(),
111 inheritance_map: HashMap::new(),
112 class_methods: HashMap::new(),
113 })
114}
115
116pub struct FunctionCallReplacer<'a> {
118 replacements_info: HashMap<String, ReplaceInfo>,
119 replacements: Vec<(Range<usize>, String)>,
120 #[allow(dead_code)]
121 source_module: &'a PythonModule<'a>,
122 #[allow(dead_code)]
123 type_introspection: TypeIntrospectionMethod,
124 #[allow(dead_code)]
125 file_path: String,
126 #[allow(dead_code)]
127 module_name: String,
128 import_map: HashMap<String, String>, }
130
131impl<'a> FunctionCallReplacer<'a> {
132 pub fn new(
133 replacements: HashMap<String, ReplaceInfo>,
134 source_module: &'a PythonModule<'a>,
135 type_introspection: TypeIntrospectionMethod,
136 file_path: String,
137 module_name: String,
138 ) -> Self {
139 let mut replacer = Self {
140 replacements_info: replacements,
141 replacements: Vec::new(),
142 source_module,
143 type_introspection,
144 file_path,
145 module_name,
146 import_map: HashMap::new(),
147 };
148 replacer.collect_imports();
150 replacer
151 }
152
153 pub fn get_replacements(self) -> Vec<(Range<usize>, String)> {
154 self.replacements
155 }
156
157 fn collect_imports(&mut self) {
159 for stmt in self.source_module.ast() {
160 match stmt {
161 AstStmt::Import(import) => {
162 for alias in &import.names {
164 let module_name = alias.name.to_string();
165 let alias_name = alias
166 .asname
167 .as_ref()
168 .map(|id| id.to_string())
169 .unwrap_or_else(|| module_name.clone());
170 self.import_map.insert(alias_name, module_name);
171 }
172 }
173 AstStmt::ImportFrom(import_from) => {
174 if let Some(module) = &import_from.module {
175 let module_str = module.to_string();
176 for alias in &import_from.names {
178 let imported_name = alias.name.to_string();
179 let alias_name = alias
180 .asname
181 .as_ref()
182 .map(|id| id.to_string())
183 .unwrap_or_else(|| imported_name.clone());
184
185 if alias.asname.is_none() {
188 let full_name = format!("{}.{}", module_str, imported_name);
189 self.import_map.insert(alias_name, full_name);
190 }
191 }
192 }
193 }
194 _ => {}
195 }
196 }
197 }
198}
199
200impl<'a> FunctionCallReplacer<'a> {
201 pub fn visit_expr(&mut self, expr: &'a AstExpr) {
202 match expr {
203 AstExpr::Call(call) => {
204 if let Some(replacement) = self.try_replace_magic_method_call(call) {
206 let range = expr.range();
207 let byte_range = std::ops::Range {
208 start: usize::from(range.start()),
209 end: usize::from(range.end()),
210 };
211 self.replacements.push((byte_range, replacement));
212 } else if let Some(func_name) = self.get_function_name(&call.func) {
213 if let Some(replace_info) = self.replacements_info.get(&func_name) {
215 let replacement = self.build_replacement(replace_info, call);
218
219 let range = expr.range();
221 let byte_range = std::ops::Range {
222 start: usize::from(range.start()),
223 end: usize::from(range.end()),
224 };
225
226 self.replacements.push((byte_range, replacement));
227 }
228 }
229
230 for arg in &call.args {
232 self.visit_expr(arg);
233 }
234 }
235 AstExpr::Attribute(attr) => {
236 self.visit_expr(&attr.value);
237 }
238 AstExpr::BinOp(binop) => {
239 self.visit_expr(&binop.left);
240 self.visit_expr(&binop.right);
241 }
242 AstExpr::UnaryOp(unaryop) => {
243 self.visit_expr(&unaryop.operand);
244 }
245 AstExpr::IfExp(ifexp) => {
246 self.visit_expr(&ifexp.test);
247 self.visit_expr(&ifexp.body);
248 self.visit_expr(&ifexp.orelse);
249 }
250 AstExpr::Dict(dict) => {
251 for key in dict.keys.iter().flatten() {
252 self.visit_expr(key);
253 }
254 for value in &dict.values {
255 self.visit_expr(value);
256 }
257 }
258 AstExpr::Set(set) => {
259 for elt in &set.elts {
260 self.visit_expr(elt);
261 }
262 }
263 AstExpr::ListComp(comp) => {
264 self.visit_expr(&comp.elt);
265 }
267 AstExpr::SetComp(comp) => {
268 self.visit_expr(&comp.elt);
269 }
271 AstExpr::DictComp(comp) => {
272 self.visit_expr(&comp.key);
273 self.visit_expr(&comp.value);
274 }
276 AstExpr::GeneratorExp(gen) => {
277 self.visit_expr(&gen.elt);
278 }
280 AstExpr::Await(await_expr) => {
281 self.visit_expr(&await_expr.value);
282 }
283 AstExpr::Yield(yield_expr) => {
284 if let Some(value) = &yield_expr.value {
285 self.visit_expr(value);
286 }
287 }
288 AstExpr::YieldFrom(yieldfrom) => {
289 self.visit_expr(&yieldfrom.value);
290 }
291 AstExpr::Compare(compare) => {
292 self.visit_expr(&compare.left);
293 for comparator in &compare.comparators {
294 self.visit_expr(comparator);
295 }
296 }
297 AstExpr::Lambda(lambda) => {
298 self.visit_expr(&lambda.body);
299 }
300 AstExpr::List(list) => {
301 for elt in &list.elts {
302 self.visit_expr(elt);
303 }
304 }
305 AstExpr::Tuple(tuple) => {
306 for elt in &tuple.elts {
307 self.visit_expr(elt);
308 }
309 }
310 AstExpr::Slice(slice) => {
311 if let Some(lower) = &slice.lower {
312 self.visit_expr(lower);
313 }
314 if let Some(upper) = &slice.upper {
315 self.visit_expr(upper);
316 }
317 if let Some(step) = &slice.step {
318 self.visit_expr(step);
319 }
320 }
321 AstExpr::Subscript(subscript) => {
322 self.visit_expr(&subscript.value);
323 self.visit_expr(&subscript.slice);
324 }
325 AstExpr::Starred(starred) => {
326 self.visit_expr(&starred.value);
327 }
328 AstExpr::NamedExpr(named) => {
329 self.visit_expr(&named.target);
330 self.visit_expr(&named.value);
331 }
332 AstExpr::Name(_) | AstExpr::Constant(_) => {
333 }
335 _ => {
336 }
338 }
339 }
340
341 pub fn visit_stmt(&mut self, stmt: &'a AstStmt) {
342 match stmt {
343 AstStmt::FunctionDef(func) => {
344 for stmt in &func.body {
346 self.visit_stmt(stmt);
347 }
348 }
349 AstStmt::AsyncFunctionDef(func) => {
350 for stmt in &func.body {
352 self.visit_stmt(stmt);
353 }
354 }
355 AstStmt::ClassDef(class) => {
356 for stmt in &class.body {
358 self.visit_stmt(stmt);
359 }
360 }
361 AstStmt::Return(ret) => {
362 if let Some(value) = &ret.value {
363 self.visit_expr(value);
364 }
365 }
366 AstStmt::Delete(del) => {
367 for target in &del.targets {
368 self.visit_expr(target);
369 }
370 }
371 AstStmt::Assign(assign) => {
372 self.visit_expr(&assign.value);
373 for target in &assign.targets {
374 self.visit_expr(target);
375 }
376 }
377 AstStmt::AugAssign(augassign) => {
378 self.visit_expr(&augassign.target);
379 self.visit_expr(&augassign.value);
380 }
381 AstStmt::AnnAssign(annassign) => {
382 self.visit_expr(&annassign.target);
383 if let Some(value) = &annassign.value {
384 self.visit_expr(value);
385 }
386 }
387 AstStmt::For(for_stmt) => {
388 self.visit_expr(&for_stmt.target);
389 self.visit_expr(&for_stmt.iter);
390 for stmt in &for_stmt.body {
391 self.visit_stmt(stmt);
392 }
393 for stmt in &for_stmt.orelse {
394 self.visit_stmt(stmt);
395 }
396 }
397 AstStmt::AsyncFor(for_stmt) => {
398 self.visit_expr(&for_stmt.target);
399 self.visit_expr(&for_stmt.iter);
400 for stmt in &for_stmt.body {
401 self.visit_stmt(stmt);
402 }
403 for stmt in &for_stmt.orelse {
404 self.visit_stmt(stmt);
405 }
406 }
407 AstStmt::While(while_stmt) => {
408 self.visit_expr(&while_stmt.test);
409 for stmt in &while_stmt.body {
410 self.visit_stmt(stmt);
411 }
412 for stmt in &while_stmt.orelse {
413 self.visit_stmt(stmt);
414 }
415 }
416 AstStmt::If(if_stmt) => {
417 self.visit_expr(&if_stmt.test);
418 for stmt in &if_stmt.body {
419 self.visit_stmt(stmt);
420 }
421 for stmt in &if_stmt.orelse {
422 self.visit_stmt(stmt);
423 }
424 }
425 AstStmt::With(with_stmt) => {
426 for item in &with_stmt.items {
427 self.visit_expr(&item.context_expr);
428 }
429 for stmt in &with_stmt.body {
430 self.visit_stmt(stmt);
431 }
432 }
433 AstStmt::AsyncWith(with_stmt) => {
434 for item in &with_stmt.items {
435 self.visit_expr(&item.context_expr);
436 }
437 for stmt in &with_stmt.body {
438 self.visit_stmt(stmt);
439 }
440 }
441 AstStmt::Raise(raise_stmt) => {
442 if let Some(exc) = &raise_stmt.exc {
443 self.visit_expr(exc);
444 }
445 if let Some(cause) = &raise_stmt.cause {
446 self.visit_expr(cause);
447 }
448 }
449 AstStmt::Try(try_stmt) => {
450 for stmt in &try_stmt.body {
451 self.visit_stmt(stmt);
452 }
453 for handler in &try_stmt.handlers {
454 match handler {
455 AstExceptHandler::ExceptHandler(h) => {
456 for stmt in &h.body {
457 self.visit_stmt(stmt);
458 }
459 }
460 }
461 }
462 for stmt in &try_stmt.orelse {
463 self.visit_stmt(stmt);
464 }
465 for stmt in &try_stmt.finalbody {
466 self.visit_stmt(stmt);
467 }
468 }
469 AstStmt::Assert(assert_stmt) => {
470 self.visit_expr(&assert_stmt.test);
471 if let Some(msg) = &assert_stmt.msg {
472 self.visit_expr(msg);
473 }
474 }
475 AstStmt::Expr(expr_stmt) => {
476 self.visit_expr(&expr_stmt.value);
477 }
478 AstStmt::Pass(_) | AstStmt::Break(_) | AstStmt::Continue(_) => {
479 }
481 _ => {
482 }
484 }
485 }
486
487 fn get_function_name(&self, expr: &AstExpr) -> Option<String> {
488 match expr {
489 AstExpr::Name(name) => {
490 let simple_name = name.id.to_string();
491
492 if let Some(full_name) = self.import_map.get(&simple_name) {
494 if self.replacements_info.contains_key(full_name) {
496 return Some(full_name.clone());
497 }
498 }
499
500 let qualified_name = format!("{}.{}", self.module_name, simple_name);
502 #[allow(clippy::map_entry)]
504 if self.replacements_info.contains_key(&qualified_name) {
506 Some(qualified_name)
507 } else if self.replacements_info.contains_key(&simple_name) {
508 Some(simple_name)
509 } else {
510 None
511 }
512 }
513 AstExpr::Attribute(attr) => {
514 let method_name = attr.attr.to_string();
516
517 for key in self.replacements_info.keys() {
519 if key.ends_with(&format!(".{}", method_name)) {
520 return Some(key.clone());
521 }
522 }
523
524 Some(method_name)
526 }
527 _ => None,
528 }
529 }
530
531 fn build_replacement(
532 &self,
533 replace_info: &ReplaceInfo,
534 call: &rustpython_ast::ExprCall,
535 ) -> String {
536 let mut replacement = replace_info.replacement_expr.clone();
537
538 if replacement.starts_with(&format!("{}.", self.module_name)) {
540 replacement = replacement[self.module_name.len() + 1..].to_string();
541 }
542
543 if replacement.contains("(*args, **kwargs)") {
545 let func_name = replacement.split('(').next().unwrap_or(&replacement);
547 let mut new_call = format!("{}(", func_name);
548
549 for (i, arg) in call.args.iter().enumerate() {
551 if i > 0 {
552 new_call.push_str(", ");
553 }
554 new_call.push_str(&self.expr_to_source(arg));
555 }
556
557 if !call.keywords.is_empty() {
559 if !call.args.is_empty() {
560 new_call.push_str(", ");
561 }
562 for (i, keyword) in call.keywords.iter().enumerate() {
563 if i > 0 {
564 new_call.push_str(", ");
565 }
566 if let Some(arg_name) = &keyword.arg {
567 new_call.push_str(&format!(
568 "{}={}",
569 arg_name,
570 self.expr_to_source(&keyword.value)
571 ));
572 } else {
573 new_call.push_str(&format!("**{}", self.expr_to_source(&keyword.value)));
575 }
576 }
577 }
578
579 new_call.push(')');
580 return new_call;
581 }
582
583 if !replacement.contains("{") {
585 return replacement;
586 }
587
588 if replacement.contains("{self}") || replacement.contains("{cls}") {
590 if let AstExpr::Attribute(attr) = call.func.as_ref() {
592 let receiver = self.expr_to_source(&attr.value);
593 replacement = replacement.replace("{self}", &receiver);
594 replacement = replacement.replace("{cls}", &receiver);
595 }
596 }
597
598 let mut arg_map = HashMap::new();
600
601 let params_start = if !replace_info.parameters.is_empty()
603 && (replace_info.parameters[0].name == "self"
604 || replace_info.parameters[0].name == "cls")
605 {
606 1
607 } else {
608 0
609 };
610
611 for (i, arg) in call.args.iter().enumerate() {
613 if let Some(param) = replace_info.parameters.get(i + params_start) {
614 let arg_str = self.expr_to_source(arg);
615 arg_map.insert(param.name.clone(), arg_str);
616 }
617 }
618
619 for keyword in &call.keywords {
621 if let Some(arg_name) = &keyword.arg {
622 let arg_str = self.expr_to_source(&keyword.value);
623 arg_map.insert(arg_name.to_string(), arg_str);
624 } else {
625 let arg_str = self.expr_to_source(&keyword.value);
627 arg_map.insert("**kwargs".to_string(), format!("**{}", arg_str));
628 }
629 }
630
631 let _has_varargs = replace_info.parameters.iter().any(|p| p.is_vararg);
633 let _has_kwargs = replace_info.parameters.iter().any(|p| p.is_kwarg);
634
635 for (param_name, arg_value) in &arg_map {
637 let placeholder = format!("{{{}}}", param_name);
638 replacement = replacement.replace(&placeholder, arg_value);
639 }
640
641 if replacement.contains("{*args}") {
643 let named_param_count = replace_info
645 .parameters
646 .iter()
647 .take_while(|p| !p.is_vararg && !p.is_kwarg && !p.is_kwonly)
648 .count();
649
650 let actual_named_count = named_param_count.saturating_sub(params_start);
652
653 let extra_args: Vec<String> = call
655 .args
656 .iter()
657 .skip(actual_named_count)
658 .map(|arg| self.expr_to_source(arg))
659 .collect();
660
661 let args_str = extra_args.join(", ");
662
663 if !args_str.is_empty() {
665 replacement = replacement.replace("{*args}", &args_str);
666 } else {
667 replacement = replacement.replace(", {*args}", "");
668 replacement = replacement.replace("{*args}", "");
669 }
670 }
671
672 if replacement.contains("{**kwargs}") {
673 let kwargs_strs: Vec<String> = call
675 .keywords
676 .iter()
677 .map(|kw| {
678 if let Some(arg) = &kw.arg {
679 format!("{}={}", arg, self.expr_to_source(&kw.value))
680 } else {
681 format!("**{}", self.expr_to_source(&kw.value))
682 }
683 })
684 .collect();
685
686 if !kwargs_strs.is_empty() {
687 let kwargs_str = kwargs_strs.join(", ");
688 replacement = replacement.replace("{**kwargs}", &kwargs_str);
689 } else {
690 replacement = replacement.replace(", {**kwargs}", "");
691 replacement = replacement.replace("{**kwargs}", "");
692 }
693 }
694
695 let mut cleaned = replacement.clone();
697
698 for param in &replace_info.parameters {
701 if param.has_default && !arg_map.contains_key(¶m.name) {
702 let placeholder = format!("{{{}}}", param.name);
703
704 cleaned = cleaned.replace(&format!(", {}={}", param.name, placeholder), "");
707 cleaned = cleaned.replace(&format!("{}={}, ", param.name, placeholder), "");
708 cleaned = cleaned.replace(&format!("{}={}", param.name, placeholder), "");
709
710 }
713 }
714
715 while cleaned.contains(", ,") {
718 cleaned = cleaned.replace(", ,", ",");
719 }
720 cleaned = cleaned.replace(", )", ")");
722 cleaned = cleaned.replace("(, ", "(");
724
725 cleaned = cleaned.replace(", {*args}", "");
727 cleaned = cleaned.replace("{*args}, ", "");
728 cleaned = cleaned.replace("({*args})", "()");
729 cleaned = cleaned.replace("{*args}", "");
730
731 cleaned = cleaned.replace(", {**kwargs}", "");
732 cleaned = cleaned.replace("{**kwargs}, ", "");
733 cleaned = cleaned.replace("({**kwargs})", "()");
734 cleaned = cleaned.replace("{**kwargs}", "");
735
736 cleaned
737 }
738
739 fn try_replace_magic_method_call(&self, call: &rustpython_ast::ExprCall) -> Option<String> {
740 if let AstExpr::Name(name) = call.func.as_ref() {
742 let magic_method_map = match name.id.as_str() {
743 "int" => "__int__",
744 "repr" => "__repr__",
745 "bool" => "__bool__",
746 "len" => "__len__",
747 "str" => "__str__",
748 "float" => "__float__",
749 "bytes" => "__bytes__",
750 "hash" => "__hash__",
751 _ => return None,
752 };
753
754 if call.args.len() != 1 {
756 return None;
757 }
758
759 let obj_expr = &call.args[0];
760 let obj_str = self.expr_to_source(obj_expr);
761
762 for (key, replace_info) in &self.replacements_info {
765 if key.ends_with(&format!(".{}", magic_method_map)) {
767 if let Some(class_name) = key.rsplit('.').nth(1) {
769 if !self.should_apply_magic_method_replacement(obj_expr, class_name) {
773 continue;
774 }
775
776 let replacement_expr = &replace_info.replacement_expr;
781
782 let patterns = [
785 format!("{}({{self}}.", name.id), format!("{}.{}({{self}}.", self.module_name, name.id), ];
788
789 for builtin_start in &patterns {
790 if replacement_expr.starts_with(builtin_start) {
791 if let Some(inner_part) =
792 replacement_expr.strip_prefix(builtin_start)
793 {
794 if inner_part.ends_with("())") {
796 if let Some(method_name) = inner_part.strip_suffix("())") {
797 let result = format!("{}.{}()", obj_str, method_name);
798 return Some(result);
799 }
800 }
801 else if inner_part.ends_with(")") {
803 if let Some(attr_name) = inner_part.strip_suffix(")") {
804 let result = format!("{}.{}", obj_str, attr_name);
805 return Some(result);
806 }
807 }
808 }
809 }
810 }
811
812 let method_expr = if replacement_expr.starts_with("{self}.") {
814 replacement_expr.strip_prefix("{self}.")
815 } else {
816 replacement_expr.strip_prefix("self.")
817 };
818
819 if let Some(method_part) = method_expr {
820 if let Some(method_name) = method_part.strip_suffix("()") {
821 let result = format!("{}.{}()", obj_str, method_name);
822 return Some(result);
823 }
824 }
825
826 if replacement_expr.contains("({self})") {
828 let mut result =
829 replacement_expr.replace("({self})", &format!("({})", obj_str));
830 if result.starts_with(&format!("{}.", self.module_name)) {
832 result = result[self.module_name.len() + 1..].to_string();
833 }
834 return Some(result);
835 }
836
837 if replacement_expr.contains("(self)") {
839 let result =
840 replacement_expr.replace("(self)", &format!("({})", obj_str));
841 return Some(result);
842 }
843
844 if !replacement_expr.contains("{") && !replacement_expr.contains("(") {
847 return Some(replacement_expr.clone());
849 }
850 }
851 }
852 }
853 }
854
855 None
856 }
857
858 fn should_apply_magic_method_replacement(&self, obj_expr: &AstExpr, _class_name: &str) -> bool {
859 match obj_expr {
861 AstExpr::Name(_) => true,
863
864 AstExpr::Attribute(_) => false,
867 AstExpr::Subscript(_) => false,
868 AstExpr::Call(_) => false,
869
870 _ => false,
876 }
877 }
878
879 fn expr_to_source(&self, expr: &AstExpr) -> String {
880 self.reconstruct_expr(expr)
882 }
883
884 #[allow(clippy::only_used_in_recursion)] fn reconstruct_expr(&self, expr: &AstExpr) -> String {
886 match expr {
887 AstExpr::Constant(c) => match &c.value {
888 rustpython_ast::Constant::Str(s) => {
889 format!(
891 "\"{}\"",
892 s.chars()
893 .map(|c| match c {
894 '"' => "\\\"".to_string(),
895 '\\' => "\\\\".to_string(),
896 '\n' => "\\n".to_string(),
897 '\r' => "\\r".to_string(),
898 '\t' => "\\t".to_string(),
899 c if c.is_control() => format!("\\x{:02x}", c as u8),
900 c => c.to_string(),
901 })
902 .collect::<String>()
903 )
904 }
905 rustpython_ast::Constant::Int(i) => i.to_string(),
906 rustpython_ast::Constant::Float(f) => f.to_string(),
907 rustpython_ast::Constant::Bool(b) => {
908 if *b {
909 "True".to_string()
910 } else {
911 "False".to_string()
912 }
913 }
914 rustpython_ast::Constant::None => "None".to_string(),
915 rustpython_ast::Constant::Bytes(b) => {
916 let escaped = b
918 .iter()
919 .map(|&byte| match byte {
920 b'"' => "\\\"".to_string(),
921 b'\\' => "\\\\".to_string(),
922 b'\n' => "\\n".to_string(),
923 b'\r' => "\\r".to_string(),
924 b'\t' => "\\t".to_string(),
925 b'\0' => "\\x00".to_string(),
926 b if b.is_ascii_graphic() || b == b' ' => (b as char).to_string(),
927 b => format!("\\x{:02x}", b),
928 })
929 .collect::<String>();
930 format!("b\"{}\"", escaped)
931 }
932 rustpython_ast::Constant::Complex { real, imag } => {
933 if *real == 0.0 {
934 format!("{}j", imag)
935 } else if *imag >= 0.0 {
936 format!("{} + {}j", real, imag)
937 } else {
938 format!("{} - {}j", real, -imag)
939 }
940 }
941 rustpython_ast::Constant::Ellipsis => "...".to_string(),
942 _ => "...".to_string(),
943 },
944 AstExpr::Name(name) => name.id.to_string(),
945 AstExpr::Attribute(attr) => {
946 format!("{}.{}", self.reconstruct_expr(&attr.value), attr.attr)
947 }
948 AstExpr::Subscript(subscript) => {
949 format!(
950 "{}[{}]",
951 self.reconstruct_expr(&subscript.value),
952 self.reconstruct_expr(&subscript.slice)
953 )
954 }
955 AstExpr::Slice(slice) => {
956 let lower = slice
957 .lower
958 .as_ref()
959 .map(|e| self.reconstruct_expr(e))
960 .unwrap_or_default();
961 let upper = slice
962 .upper
963 .as_ref()
964 .map(|e| self.reconstruct_expr(e))
965 .unwrap_or_default();
966 let step = slice
967 .step
968 .as_ref()
969 .map(|e| format!(":{}", self.reconstruct_expr(e)))
970 .unwrap_or_default();
971 format!("{}:{}{}", lower, upper, step)
972 }
973 AstExpr::BinOp(binop) => {
974 let left = self.reconstruct_expr(&binop.left);
975 let right = self.reconstruct_expr(&binop.right);
976 let op = match binop.op {
977 rustpython_ast::Operator::Add => "+",
978 rustpython_ast::Operator::Sub => "-",
979 rustpython_ast::Operator::Mult => "*",
980 rustpython_ast::Operator::MatMult => "@",
981 rustpython_ast::Operator::Div => "/",
982 rustpython_ast::Operator::Mod => "%",
983 rustpython_ast::Operator::Pow => "**",
984 rustpython_ast::Operator::LShift => "<<",
985 rustpython_ast::Operator::RShift => ">>",
986 rustpython_ast::Operator::BitOr => "|",
987 rustpython_ast::Operator::BitXor => "^",
988 rustpython_ast::Operator::BitAnd => "&",
989 rustpython_ast::Operator::FloorDiv => "//",
990 };
991 format!("{} {} {}", left, op, right)
992 }
993 AstExpr::UnaryOp(unaryop) => {
994 let operand = self.reconstruct_expr(&unaryop.operand);
995 let op = match unaryop.op {
996 rustpython_ast::UnaryOp::Invert => "~",
997 rustpython_ast::UnaryOp::Not => "not ",
998 rustpython_ast::UnaryOp::UAdd => "+",
999 rustpython_ast::UnaryOp::USub => "-",
1000 };
1001 format!("{}{}", op, operand)
1002 }
1003 AstExpr::BoolOp(boolop) => {
1004 let op = match boolop.op {
1005 rustpython_ast::BoolOp::And => " and ",
1006 rustpython_ast::BoolOp::Or => " or ",
1007 };
1008 boolop
1009 .values
1010 .iter()
1011 .map(|v| self.reconstruct_expr(v))
1012 .collect::<Vec<_>>()
1013 .join(op)
1014 }
1015 AstExpr::Compare(compare) => {
1016 let mut result = self.reconstruct_expr(&compare.left);
1017 for (op, comparator) in compare.ops.iter().zip(&compare.comparators) {
1018 let op_str = match op {
1019 rustpython_ast::CmpOp::Eq => " == ",
1020 rustpython_ast::CmpOp::NotEq => " != ",
1021 rustpython_ast::CmpOp::Lt => " < ",
1022 rustpython_ast::CmpOp::LtE => " <= ",
1023 rustpython_ast::CmpOp::Gt => " > ",
1024 rustpython_ast::CmpOp::GtE => " >= ",
1025 rustpython_ast::CmpOp::Is => " is ",
1026 rustpython_ast::CmpOp::IsNot => " is not ",
1027 rustpython_ast::CmpOp::In => " in ",
1028 rustpython_ast::CmpOp::NotIn => " not in ",
1029 };
1030 result.push_str(op_str);
1031 result.push_str(&self.reconstruct_expr(comparator));
1032 }
1033 result
1034 }
1035 AstExpr::Call(call) => {
1036 let func = self.reconstruct_expr(&call.func);
1037 let args: Vec<String> = call
1039 .args
1040 .iter()
1041 .map(|arg| self.reconstruct_expr(arg))
1042 .chain(call.keywords.iter().map(|keyword| {
1043 if let Some(arg_name) = &keyword.arg {
1044 format!("{}={}", arg_name, self.reconstruct_expr(&keyword.value))
1045 } else {
1046 format!("**{}", self.reconstruct_expr(&keyword.value))
1047 }
1048 }))
1049 .collect();
1050
1051 format!("{}({})", func, args.join(", "))
1052 }
1053 AstExpr::List(list) => {
1054 let elements: Vec<String> =
1055 list.elts.iter().map(|e| self.reconstruct_expr(e)).collect();
1056 format!("[{}]", elements.join(", "))
1057 }
1058 AstExpr::Tuple(tuple) => {
1059 let elements: Vec<String> = tuple
1060 .elts
1061 .iter()
1062 .map(|e| self.reconstruct_expr(e))
1063 .collect();
1064 if elements.len() == 1 {
1065 format!("({},)", elements[0])
1066 } else {
1067 format!("({})", elements.join(", "))
1068 }
1069 }
1070 AstExpr::Dict(dict) => {
1071 let items: Vec<String> = dict
1072 .keys
1073 .iter()
1074 .zip(&dict.values)
1075 .map(|(key, value)| {
1076 if let Some(k) = key {
1077 format!(
1078 "{}: {}",
1079 self.reconstruct_expr(k),
1080 self.reconstruct_expr(value)
1081 )
1082 } else {
1083 format!("**{}", self.reconstruct_expr(value))
1084 }
1085 })
1086 .collect();
1087 format!("{{{}}}", items.join(", "))
1088 }
1089 AstExpr::Set(set) => {
1090 let elements: Vec<String> =
1091 set.elts.iter().map(|e| self.reconstruct_expr(e)).collect();
1092 if elements.is_empty() {
1093 "set()".to_string()
1094 } else {
1095 format!("{{{}}}", elements.join(", "))
1096 }
1097 }
1098 AstExpr::ListComp(comp) => {
1099 let elt = self.reconstruct_expr(&comp.elt);
1100
1101 let mut generators = Vec::new();
1103 for gen in &comp.generators {
1104 let target = self.reconstruct_expr(&gen.target);
1105 let iter = self.reconstruct_expr(&gen.iter);
1106 let mut gen_str = format!("for {} in {}", target, iter);
1107
1108 if !gen.ifs.is_empty() {
1110 let conds: Vec<String> = gen
1111 .ifs
1112 .iter()
1113 .map(|if_expr| self.reconstruct_expr(if_expr))
1114 .collect();
1115 gen_str.push_str(&format!(" if {}", conds.join(" if ")));
1116 }
1117
1118 generators.push(gen_str);
1119 }
1120
1121 format!("[{} {}]", elt, generators.join(" "))
1122 }
1123 AstExpr::SetComp(comp) => {
1124 let conditions = if comp.generators[0].ifs.is_empty() {
1125 String::new()
1126 } else {
1127 let conds: Vec<String> = comp.generators[0]
1128 .ifs
1129 .iter()
1130 .map(|if_expr| self.reconstruct_expr(if_expr))
1131 .collect();
1132 format!(" if {}", conds.join(" if "))
1133 };
1134 format!(
1135 "{{{} for {} in {}{}}}",
1136 self.reconstruct_expr(&comp.elt),
1137 self.reconstruct_expr(&comp.generators[0].target),
1138 self.reconstruct_expr(&comp.generators[0].iter),
1139 conditions
1140 )
1141 }
1142 AstExpr::DictComp(comp) => {
1143 let conditions = if comp.generators[0].ifs.is_empty() {
1144 String::new()
1145 } else {
1146 let conds: Vec<String> = comp.generators[0]
1147 .ifs
1148 .iter()
1149 .map(|if_expr| self.reconstruct_expr(if_expr))
1150 .collect();
1151 format!(" if {}", conds.join(" if "))
1152 };
1153 format!(
1154 "{{{}: {} for {} in {}{}}}",
1155 self.reconstruct_expr(&comp.key),
1156 self.reconstruct_expr(&comp.value),
1157 self.reconstruct_expr(&comp.generators[0].target),
1158 self.reconstruct_expr(&comp.generators[0].iter),
1159 conditions
1160 )
1161 }
1162 AstExpr::GeneratorExp(gen) => {
1163 let conditions = if gen.generators[0].ifs.is_empty() {
1164 String::new()
1165 } else {
1166 let conds: Vec<String> = gen.generators[0]
1167 .ifs
1168 .iter()
1169 .map(|if_expr| self.reconstruct_expr(if_expr))
1170 .collect();
1171 format!(" if {}", conds.join(" if "))
1172 };
1173 format!(
1174 "({} for {} in {}{})",
1175 self.reconstruct_expr(&gen.elt),
1176 self.reconstruct_expr(&gen.generators[0].target),
1177 self.reconstruct_expr(&gen.generators[0].iter),
1178 conditions
1179 )
1180 }
1181 AstExpr::IfExp(ifexp) => {
1182 format!(
1183 "{} if {} else {}",
1184 self.reconstruct_expr(&ifexp.body),
1185 self.reconstruct_expr(&ifexp.test),
1186 self.reconstruct_expr(&ifexp.orelse)
1187 )
1188 }
1189 AstExpr::Lambda(lambda) => {
1190 let args: Vec<String> = lambda
1191 .args
1192 .args
1193 .iter()
1194 .map(|arg| arg.def.arg.to_string())
1195 .collect();
1196 format!(
1197 "lambda {}: {}",
1198 args.join(", "),
1199 self.reconstruct_expr(&lambda.body)
1200 )
1201 }
1202 AstExpr::Starred(starred) => {
1203 format!("*{}", self.reconstruct_expr(&starred.value))
1204 }
1205 AstExpr::Yield(yield_expr) => {
1206 if let Some(value) = &yield_expr.value {
1207 format!("yield {}", self.reconstruct_expr(value))
1208 } else {
1209 "yield".to_string()
1210 }
1211 }
1212 AstExpr::YieldFrom(yieldfrom) => {
1213 format!("yield from {}", self.reconstruct_expr(&yieldfrom.value))
1214 }
1215 AstExpr::Await(await_expr) => {
1216 format!("await {}", self.reconstruct_expr(&await_expr.value))
1217 }
1218 AstExpr::JoinedStr(joined) => {
1219 let mut result = String::from("f\"");
1220 for value in &joined.values {
1221 match value {
1222 AstExpr::Constant(c) => {
1223 if let rustpython_ast::Constant::Str(s) = &c.value {
1224 result.push_str(s);
1225 }
1226 }
1227 AstExpr::FormattedValue(fmt) => {
1228 result.push('{');
1229 result.push_str(&self.reconstruct_expr(&fmt.value));
1230 match fmt.conversion {
1231 rustpython_ast::ConversionFlag::Str => result.push_str("!s"),
1232 rustpython_ast::ConversionFlag::Repr => result.push_str("!r"),
1233 rustpython_ast::ConversionFlag::Ascii => result.push_str("!a"),
1234 rustpython_ast::ConversionFlag::None => {}
1235 }
1236 if let Some(spec) = &fmt.format_spec {
1237 result.push(':');
1238 match &**spec {
1239 AstExpr::Constant(c) => {
1240 if let rustpython_ast::Constant::Str(s) = &c.value {
1241 result.push_str(s);
1242 }
1243 }
1244 AstExpr::JoinedStr(_) => {
1245 let reconstructed = self.reconstruct_expr(spec);
1248 if reconstructed.starts_with("f\"")
1250 && reconstructed.ends_with('"')
1251 {
1252 result.push_str(
1253 &reconstructed[2..reconstructed.len() - 1],
1254 );
1255 } else {
1256 result.push_str(&reconstructed);
1257 }
1258 }
1259 _ => result.push_str(&self.reconstruct_expr(spec)),
1260 }
1261 }
1262 result.push('}');
1263 }
1264 _ => {
1265 result.push('{');
1266 result.push_str(&self.reconstruct_expr(value));
1267 result.push('}');
1268 }
1269 }
1270 }
1271 result.push('"');
1272 result
1273 }
1274 AstExpr::FormattedValue(fmt) => {
1275 let mut result = String::new();
1277 result.push('{');
1278 result.push_str(&self.reconstruct_expr(&fmt.value));
1279 match fmt.conversion {
1280 rustpython_ast::ConversionFlag::Str => result.push_str("!s"),
1281 rustpython_ast::ConversionFlag::Repr => result.push_str("!r"),
1282 rustpython_ast::ConversionFlag::Ascii => result.push_str("!a"),
1283 rustpython_ast::ConversionFlag::None => {}
1284 }
1285 if let Some(spec) = &fmt.format_spec {
1286 result.push(':');
1287 match &**spec {
1288 AstExpr::Constant(c) => {
1289 if let rustpython_ast::Constant::Str(s) = &c.value {
1290 result.push_str(s);
1291 }
1292 }
1293 _ => result.push_str(&self.reconstruct_expr(spec)),
1294 }
1295 }
1296 result.push('}');
1297 result
1298 }
1299 AstExpr::NamedExpr(named) => {
1300 format!(
1301 "{} := {}",
1302 self.reconstruct_expr(&named.target),
1303 self.reconstruct_expr(&named.value)
1304 )
1305 }
1306 }
1307 }
1308}
1309
1310pub fn apply_replacements(source: &str, mut replacements: Vec<(Range<usize>, String)>) -> String {
1312 replacements.sort_by_key(|(range, _)| std::cmp::Reverse(range.start));
1314
1315 let mut result = source.to_string();
1316
1317 for (range, replacement) in replacements {
1318 let original_text = &source[range.clone()];
1319 tracing::debug!(
1320 "Applying replacement at {:?}: '{}' -> '{}'",
1321 range,
1322 original_text,
1323 replacement
1324 );
1325 result.replace_range(range, &replacement);
1326 }
1327
1328 result
1329}
1330
1331pub fn migrate_file_with_ruff(
1333 source: &str,
1334 module_name: &str,
1335 file_path: String,
1336 type_introspection: TypeIntrospectionMethod,
1337) -> Result<String> {
1338 use crate::type_introspection_context::TypeIntrospectionContext;
1340 use std::path::Path;
1341
1342 let mut type_context = TypeIntrospectionContext::new(type_introspection)?;
1343
1344 let result = crate::migrate_stub::migrate_file(
1345 source,
1346 module_name,
1347 Path::new(&file_path),
1348 &mut type_context,
1349 std::collections::HashMap::new(), std::collections::HashMap::new(), );
1352
1353 type_context.shutdown()?;
1354 result
1355}
1356
1357pub fn migrate_file_with_improved_ruff(
1360 source: &str,
1361 module_name: &str,
1362 file_path: String,
1363 type_introspection: TypeIntrospectionMethod,
1364) -> Result<String> {
1365 migrate_file_with_ruff(source, module_name, file_path, type_introspection)
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372 use super::*;
1373
1374 #[test]
1375 fn test_parse_simple() {
1376 let source = "x = 1\ny = 2";
1377 let module = PythonModule::parse(source).unwrap();
1378 assert_eq!(module.ast().len(), 2);
1379 }
1380
1381 #[test]
1382 fn test_position_mapping() {
1383 let source = "x = 1\ny = 2";
1384 let module = PythonModule::parse(source).unwrap();
1385
1386 assert_eq!(module.offset_to_position(0), Some((1, 0)));
1388
1389 assert_eq!(module.offset_to_position(6), Some((2, 0)));
1391 }
1392}