1use std::collections::HashMap;
10
11use serde::{Deserialize, Serialize};
12
13use crate::classifier::ErrorCategory;
14
15#[derive(Clone, Debug, Serialize, Deserialize)]
17pub struct CodeTransform {
18 pub description: String,
20 pub match_pattern: String,
22 pub replacement: String,
24 pub example_before: String,
26 pub example_after: String,
28}
29
30impl CodeTransform {
31 #[must_use]
33 pub fn new(
34 description: &str,
35 match_pattern: &str,
36 replacement: &str,
37 example_before: &str,
38 example_after: &str,
39 ) -> Self {
40 Self {
41 description: description.to_string(),
42 match_pattern: match_pattern.to_string(),
43 replacement: replacement.to_string(),
44 example_before: example_before.to_string(),
45 example_after: example_after.to_string(),
46 }
47 }
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct FixTemplate {
53 pub id: String,
55 pub name: String,
57 pub category: ErrorCategory,
59 pub trigger_keywords: Vec<String>,
61 pub explanation: String,
63 pub transforms: Vec<CodeTransform>,
65 pub suggestions: Vec<String>,
67 pub doc_links: Vec<String>,
69 pub priority: u32,
71}
72
73impl FixTemplate {
74 #[must_use]
76 pub fn builder(id: &str, name: &str, category: ErrorCategory) -> FixTemplateBuilder {
77 FixTemplateBuilder::new(id, name, category)
78 }
79
80 #[must_use]
82 pub fn matches(&self, error_message: &str) -> bool {
83 let lower = error_message.to_lowercase();
84 self.trigger_keywords
85 .iter()
86 .any(|kw| lower.contains(&kw.to_lowercase()))
87 }
88
89 #[must_use]
91 pub fn match_score(&self, error_message: &str) -> f32 {
92 let lower = error_message.to_lowercase();
93 let matched = self
94 .trigger_keywords
95 .iter()
96 .filter(|kw| lower.contains(&kw.to_lowercase()))
97 .count();
98
99 if self.trigger_keywords.is_empty() {
100 return 0.0;
101 }
102
103 let keyword_score = matched as f32 / self.trigger_keywords.len() as f32;
104 keyword_score * (self.priority as f32 / 100.0)
105 }
106}
107
108pub struct FixTemplateBuilder {
110 template: FixTemplate,
111}
112
113impl FixTemplateBuilder {
114 fn new(id: &str, name: &str, category: ErrorCategory) -> Self {
115 Self {
116 template: FixTemplate {
117 id: id.to_string(),
118 name: name.to_string(),
119 category,
120 trigger_keywords: Vec::new(),
121 explanation: String::new(),
122 transforms: Vec::new(),
123 suggestions: Vec::new(),
124 doc_links: Vec::new(),
125 priority: 50,
126 },
127 }
128 }
129
130 #[must_use]
132 pub fn with_keywords(mut self, keywords: &[&str]) -> Self {
133 self.template.trigger_keywords = keywords.iter().map(|s| (*s).to_string()).collect();
134 self
135 }
136
137 #[must_use]
139 pub fn with_explanation(mut self, explanation: &str) -> Self {
140 self.template.explanation = explanation.to_string();
141 self
142 }
143
144 #[must_use]
146 pub fn with_transform(mut self, transform: CodeTransform) -> Self {
147 self.template.transforms.push(transform);
148 self
149 }
150
151 #[must_use]
153 pub fn with_suggestions(mut self, suggestions: &[&str]) -> Self {
154 self.template.suggestions = suggestions.iter().map(|s| (*s).to_string()).collect();
155 self
156 }
157
158 #[must_use]
160 pub fn with_docs(mut self, links: &[&str]) -> Self {
161 self.template.doc_links = links.iter().map(|s| (*s).to_string()).collect();
162 self
163 }
164
165 #[must_use]
167 pub fn with_priority(mut self, priority: u32) -> Self {
168 self.template.priority = priority;
169 self
170 }
171
172 #[must_use]
174 pub fn build(self) -> FixTemplate {
175 self.template
176 }
177}
178
179pub struct FixTemplateRegistry {
181 templates: HashMap<ErrorCategory, Vec<FixTemplate>>,
183}
184
185impl FixTemplateRegistry {
186 #[must_use]
188 pub fn new() -> Self {
189 Self {
190 templates: HashMap::new(),
191 }
192 }
193
194 #[must_use]
196 pub fn with_rust_defaults() -> Self {
197 let mut registry = Self::new();
198 register_type_mismatch_templates(&mut registry);
199 register_borrow_checker_templates(&mut registry);
200 register_lifetime_templates(&mut registry);
201 register_trait_bound_templates(&mut registry);
202 register_import_templates(&mut registry);
203 register_syntax_templates(&mut registry);
204 register_transpiler_patterns(&mut registry);
206 registry
207 }
208
209 pub fn register(&mut self, template: FixTemplate) {
211 self.templates
212 .entry(template.category)
213 .or_default()
214 .push(template);
215 }
216
217 #[must_use]
219 pub fn get_templates(&self, category: ErrorCategory) -> &[FixTemplate] {
220 self.templates.get(&category).map_or(&[], |v| v.as_slice())
221 }
222
223 #[must_use]
225 pub fn find_matches(&self, error_message: &str) -> Vec<&FixTemplate> {
226 let mut matches: Vec<(&FixTemplate, f32)> = self
227 .templates
228 .values()
229 .flatten()
230 .filter_map(|t| {
231 let score = t.match_score(error_message);
232 if score > 0.0 {
233 Some((t, score))
234 } else {
235 None
236 }
237 })
238 .collect();
239
240 matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
241
242 matches.into_iter().map(|(t, _)| t).collect()
243 }
244
245 #[must_use]
247 pub fn find_best_match(&self, error_message: &str) -> Option<&FixTemplate> {
248 self.find_matches(error_message).into_iter().next()
249 }
250
251 #[must_use]
253 pub fn all_templates(&self) -> Vec<&FixTemplate> {
254 self.templates.values().flatten().collect()
255 }
256
257 #[must_use]
259 pub fn template_count(&self) -> usize {
260 self.templates.values().map(|v| v.len()).sum()
261 }
262}
263
264impl Default for FixTemplateRegistry {
265 fn default() -> Self {
266 Self::with_rust_defaults()
267 }
268}
269
270fn register_type_mismatch_templates(registry: &mut FixTemplateRegistry) {
275 registry.register(
277 FixTemplate::builder(
278 "type-int-convert",
279 "Integer Type Conversion",
280 ErrorCategory::TypeMismatch,
281 )
282 .with_keywords(&[
283 "expected", "found", "i32", "i64", "u32", "u64", "isize", "usize",
284 ])
285 .with_explanation(
286 "Rust has strict type checking for integers. Different integer types \
287 cannot be implicitly converted. Use explicit conversion with `as` or `.into()`.",
288 )
289 .with_transform(CodeTransform::new(
290 "Convert using `as`",
291 r"(\w+)",
292 "$1 as TARGET_TYPE",
293 "let x: i32 = some_u64;",
294 "let x: i32 = some_u64 as i32;",
295 ))
296 .with_suggestions(&[
297 "Use `as` for explicit numeric conversion",
298 "Consider using `.try_into()` for fallible conversion",
299 "Check if the source value fits in the target type",
300 ])
301 .with_docs(&["https://doc.rust-lang.org/std/convert/trait.Into.html"])
302 .with_priority(80)
303 .build(),
304 );
305
306 registry.register(
308 FixTemplate::builder("type-string-convert", "String Type Conversion", ErrorCategory::TypeMismatch)
309 .with_keywords(&["expected", "found", "String", "&str", "string", "str"])
310 .with_explanation(
311 "Rust distinguishes between owned strings (String) and string slices (&str). \
312 Use `.to_string()` to convert &str to String, or `&` / `.as_str()` for the reverse."
313 )
314 .with_transform(CodeTransform::new(
315 "Convert &str to String",
316 r#"(".*")"#,
317 r#"$1.to_string()"#,
318 r#"let s: String = "hello";"#,
319 r#"let s: String = "hello".to_string();"#,
320 ))
321 .with_transform(CodeTransform::new(
322 "Convert String to &str",
323 r"(\w+)",
324 "&$1",
325 "let s: &str = my_string;",
326 "let s: &str = &my_string;",
327 ))
328 .with_suggestions(&[
329 "Use `.to_string()` to create an owned String from &str",
330 "Use `&` or `.as_str()` to borrow a String as &str",
331 "Consider if you really need String or if &str would work",
332 ])
333 .with_priority(85)
334 .build(),
335 );
336
337 registry.register(
339 FixTemplate::builder("type-option-result", "Option/Result Type Handling", ErrorCategory::TypeMismatch)
340 .with_keywords(&["Option", "Result", "Some", "None", "Ok", "Err", "expected", "found"])
341 .with_explanation(
342 "Option and Result types wrap values. You need to unwrap them to access the inner value. \
343 Prefer `?` operator, `map`, `and_then`, or pattern matching over `unwrap()`."
344 )
345 .with_transform(CodeTransform::new(
346 "Use ? operator",
347 r"(\w+)\.unwrap\(\)",
348 "$1?",
349 "let value = some_option.unwrap();",
350 "let value = some_option?;",
351 ))
352 .with_suggestions(&[
353 "Use `?` operator to propagate errors",
354 "Use `if let Some(x) = ...` for optional unwrapping",
355 "Use `.unwrap_or_default()` for safe defaults",
356 "Use `.expect(\"message\")` for better error messages",
357 ])
358 .with_docs(&[
359 "https://doc.rust-lang.org/std/option/",
360 "https://doc.rust-lang.org/std/result/",
361 ])
362 .with_priority(90)
363 .build(),
364 );
365}
366
367fn register_borrow_checker_templates(registry: &mut FixTemplateRegistry) {
368 registry.register(
370 FixTemplate::builder(
371 "borrow-move",
372 "Cannot Move Out of Borrowed",
373 ErrorCategory::BorrowChecker,
374 )
375 .with_keywords(&["cannot move", "borrowed", "move out of"])
376 .with_explanation(
377 "You're trying to take ownership of a value that is only borrowed. \
378 You need to either clone the value or restructure your code.",
379 )
380 .with_transform(CodeTransform::new(
381 "Clone the value",
382 r"(\w+)",
383 "$1.clone()",
384 "let x = borrowed_value;",
385 "let x = borrowed_value.clone();",
386 ))
387 .with_suggestions(&[
388 "Clone the value if it implements Clone",
389 "Take ownership of the original instead of borrowing",
390 "Use a reference instead of owned value",
391 "Restructure to avoid needing ownership",
392 ])
393 .with_priority(85)
394 .build(),
395 );
396
397 registry.register(
399 FixTemplate::builder(
400 "borrow-use-after-move",
401 "Value Used After Move",
402 ErrorCategory::BorrowChecker,
403 )
404 .with_keywords(&["value used", "after move", "moved", "borrowed"])
405 .with_explanation(
406 "Once a value is moved, it can no longer be used. Clone the value before moving, \
407 or restructure your code to avoid the second use.",
408 )
409 .with_suggestions(&[
410 "Clone the value before the first use if you need it twice",
411 "Pass by reference instead of by value",
412 "Use Rc/Arc for shared ownership",
413 "Restructure to use the value only once",
414 ])
415 .with_docs(&["https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html"])
416 .with_priority(80)
417 .build(),
418 );
419
420 registry.register(
422 FixTemplate::builder(
423 "borrow-mut-conflict",
424 "Mutable Borrow Conflict",
425 ErrorCategory::BorrowChecker,
426 )
427 .with_keywords(&[
428 "mutable",
429 "immutable",
430 "borrow",
431 "cannot borrow",
432 "already borrowed",
433 ])
434 .with_explanation(
435 "Rust prevents having mutable and immutable borrows at the same time. \
436 Restructure your code to separate the borrows or use interior mutability.",
437 )
438 .with_suggestions(&[
439 "Separate the mutable and immutable operations",
440 "Use interior mutability (Cell, RefCell, Mutex)",
441 "Clone data instead of borrowing",
442 "Restructure to avoid overlapping borrows",
443 ])
444 .with_docs(&["https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html"])
445 .with_priority(85)
446 .build(),
447 );
448}
449
450fn register_lifetime_templates(registry: &mut FixTemplateRegistry) {
451 registry.register(
453 FixTemplate::builder(
454 "lifetime-short",
455 "Value Does Not Live Long Enough",
456 ErrorCategory::LifetimeError,
457 )
458 .with_keywords(&[
459 "does not live long enough",
460 "lifetime",
461 "dropped",
462 "borrowed value",
463 ])
464 .with_explanation(
465 "The borrowed value is dropped before the borrow ends. \
466 You need to ensure the value lives as long as the reference.",
467 )
468 .with_suggestions(&[
469 "Move the value to a longer-lived scope",
470 "Return an owned value instead of a reference",
471 "Use 'static lifetime for truly long-lived data",
472 "Clone the data to create owned value",
473 ])
474 .with_priority(80)
475 .build(),
476 );
477
478 registry.register(
480 FixTemplate::builder(
481 "lifetime-explicit",
482 "Explicit Lifetime Annotation Needed",
483 ErrorCategory::LifetimeError,
484 )
485 .with_keywords(&["lifetime", "annotation", "explicit", "'a", "parameter"])
486 .with_explanation(
487 "The compiler cannot infer the lifetime relationship. \
488 Add explicit lifetime parameters to clarify.",
489 )
490 .with_transform(CodeTransform::new(
491 "Add lifetime parameter",
492 r"fn (\w+)\((.*)\) -> &(\w+)",
493 "fn $1<'a>($2) -> &'a $3",
494 "fn get_str(s: &String) -> &str",
495 "fn get_str<'a>(s: &'a String) -> &'a str",
496 ))
497 .with_suggestions(&[
498 "Add lifetime parameter <'a> to function signature",
499 "Annotate references with the same lifetime",
500 "Consider returning owned data instead",
501 ])
502 .with_docs(&["https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"])
503 .with_priority(75)
504 .build(),
505 );
506}
507
508fn register_trait_bound_templates(registry: &mut FixTemplateRegistry) {
509 registry.register(
511 FixTemplate::builder(
512 "trait-not-impl",
513 "Trait Not Implemented",
514 ErrorCategory::TraitBound,
515 )
516 .with_keywords(&["trait", "not implemented", "bound", "doesn't implement"])
517 .with_explanation(
518 "The type doesn't implement a required trait. \
519 Either implement the trait or use a different approach.",
520 )
521 .with_suggestions(&[
522 "Derive the trait if possible: #[derive(Clone, Debug, ...)]",
523 "Implement the trait manually",
524 "Use a wrapper type that implements the trait",
525 "Change the function to not require the trait",
526 ])
527 .with_priority(80)
528 .build(),
529 );
530
531 registry.register(
533 FixTemplate::builder(
534 "trait-derive",
535 "Missing Derive Attribute",
536 ErrorCategory::TraitBound,
537 )
538 .with_keywords(&["Clone", "Copy", "Debug", "Default", "derive", "cannot"])
539 .with_explanation(
540 "Common traits like Clone, Copy, Debug can be automatically derived. \
541 Add #[derive(...)] to your struct or enum.",
542 )
543 .with_transform(CodeTransform::new(
544 "Add derive attribute",
545 r"struct (\w+)",
546 "#[derive(Clone, Debug)]\nstruct $1",
547 "struct MyStruct { ... }",
548 "#[derive(Clone, Debug)]\nstruct MyStruct { ... }",
549 ))
550 .with_suggestions(&[
551 "Add #[derive(Clone)] for Clone trait",
552 "Add #[derive(Debug)] for Debug trait",
553 "Add #[derive(Default)] for Default trait",
554 "Combine derives: #[derive(Clone, Debug, Default)]",
555 ])
556 .with_priority(85)
557 .build(),
558 );
559}
560
561fn register_import_templates(registry: &mut FixTemplateRegistry) {
562 registry.register(
564 FixTemplate::builder(
565 "import-not-found",
566 "Item Not Found in Scope",
567 ErrorCategory::MissingImport,
568 )
569 .with_keywords(&[
570 "not found",
571 "cannot find",
572 "unresolved",
573 "use of undeclared",
574 ])
575 .with_explanation(
576 "The item is not in scope. You need to import it with `use` \
577 or use the fully qualified path.",
578 )
579 .with_suggestions(&[
580 "Add `use` statement at the top of the file",
581 "Use fully qualified path: std::collections::HashMap",
582 "Check if the crate is in Cargo.toml dependencies",
583 "Verify the item exists in that module",
584 ])
585 .with_priority(80)
586 .build(),
587 );
588
589 registry.register(
591 FixTemplate::builder(
592 "import-std-common",
593 "Common Standard Library Imports",
594 ErrorCategory::MissingImport,
595 )
596 .with_keywords(&[
597 "HashMap", "HashSet", "Vec", "String", "Box", "Rc", "Arc", "Cell", "RefCell",
598 ])
599 .with_explanation("Common standard library types need to be imported.")
600 .with_transform(CodeTransform::new(
601 "Import HashMap",
602 "HashMap",
603 "use std::collections::HashMap;\n\nHashMap",
604 "let map: HashMap<K, V>",
605 "use std::collections::HashMap;\n\nlet map: HashMap<K, V>",
606 ))
607 .with_suggestions(&[
608 "use std::collections::{HashMap, HashSet};",
609 "use std::rc::Rc;",
610 "use std::sync::Arc;",
611 "use std::cell::{Cell, RefCell};",
612 ])
613 .with_priority(85)
614 .build(),
615 );
616}
617
618fn register_syntax_templates(registry: &mut FixTemplateRegistry) {
619 registry.register(
621 FixTemplate::builder(
622 "syntax-semicolon",
623 "Missing Semicolon",
624 ErrorCategory::SyntaxError,
625 )
626 .with_keywords(&["expected", ";", "semicolon", "statement"])
627 .with_explanation(
628 "Statements in Rust must end with a semicolon (;). \
629 Expressions that are returned don't need semicolons.",
630 )
631 .with_suggestions(&[
632 "Add semicolon at end of statement",
633 "If this is a return expression, remove the semicolon",
634 "Check for unmatched brackets or braces above",
635 ])
636 .with_priority(90)
637 .build(),
638 );
639
640 registry.register(
642 FixTemplate::builder(
643 "syntax-brackets",
644 "Unmatched Brackets",
645 ErrorCategory::SyntaxError,
646 )
647 .with_keywords(&["expected", "}", ")", "]", "unmatched", "unclosed"])
648 .with_explanation(
649 "Opening and closing brackets must be balanced. \
650 Check for missing or extra brackets.",
651 )
652 .with_suggestions(&[
653 "Count opening and closing brackets",
654 "Use editor bracket matching feature",
655 "Check recent changes for missing brackets",
656 ])
657 .with_priority(85)
658 .build(),
659 );
660}
661
662fn register_transpiler_patterns(registry: &mut FixTemplateRegistry) {
668 registry.register(
672 FixTemplate::builder(
673 "e0599-datetime-tuple",
674 "Datetime Methods on Tuple",
675 ErrorCategory::TypeMismatch,
676 )
677 .with_keywords(&["no method named", "hour", "minute", "second", "tuple"])
678 .with_explanation(
679 "Python datetime.time objects have hour/minute/second attributes. \
680 The transpiler incorrectly inferred a tuple type instead of a time type.",
681 )
682 .with_suggestions(&[
683 "Change type annotation from tuple to chrono::NaiveTime",
684 "Use tuple destructuring: let (hour, minute, second) = time;",
685 "Access tuple elements: time.0, time.1, time.2",
686 ])
687 .with_priority(90)
688 .build(),
689 );
690
691 registry.register(
693 FixTemplate::builder(
694 "e0599-date-tuple",
695 "Date Methods on Tuple",
696 ErrorCategory::TypeMismatch,
697 )
698 .with_keywords(&["no method named", "year", "month", "day", "tuple"])
699 .with_explanation(
700 "Python datetime.date objects have year/month/day attributes. \
701 The transpiler incorrectly inferred a tuple type instead of a date type.",
702 )
703 .with_suggestions(&[
704 "Change type annotation from tuple to chrono::NaiveDate",
705 "Use tuple destructuring: let (year, month, day) = date;",
706 "Access tuple elements: date.0, date.1, date.2",
707 ])
708 .with_priority(90)
709 .build(),
710 );
711
712 registry.register(
714 FixTemplate::builder(
715 "e0599-as-i64-cast",
716 "as_i64 Method Missing",
717 ErrorCategory::TypeMismatch,
718 )
719 .with_keywords(&["no method named", "as_i64", "i32", "i64"])
720 .with_explanation(
721 "Rust doesn't have an as_i64() method. Use the `as` keyword for numeric casts.",
722 )
723 .with_transform(CodeTransform::new(
724 "Replace .as_i64() with cast",
725 r"\.as_i64\(\)",
726 " as i64",
727 "value.as_i64()",
728 "value as i64",
729 ))
730 .with_suggestions(&[
731 "Use `value as i64` instead of `value.as_i64()`",
732 "For Option<i64>, use `.map(|v| v as i64)`",
733 ])
734 .with_priority(95)
735 .build(),
736 );
737
738 registry.register(
740 FixTemplate::builder(
741 "e0599-is-some-vec",
742 "is_some on Vec",
743 ErrorCategory::TypeMismatch,
744 )
745 .with_keywords(&["no method named", "is_some", "Vec"])
746 .with_explanation(
747 "is_some() is an Option method, not a Vec method. \
748 The transpiler incorrectly inferred Vec instead of Option.",
749 )
750 .with_suggestions(&[
751 "Change Vec<T> to Option<T> if checking for presence",
752 "Use !vec.is_empty() to check if Vec has elements",
753 "Wrap the Vec in Option if it's optional: Option<Vec<T>>",
754 ])
755 .with_priority(90)
756 .build(),
757 );
758
759 registry.register(
761 FixTemplate::builder(
762 "e0599-display-string",
763 "display on String",
764 ErrorCategory::TypeMismatch,
765 )
766 .with_keywords(&["no method named", "display", "String"])
767 .with_explanation(
768 "display() is a Path method, not a String method. \
769 For String formatting, just use the String directly or &str.",
770 )
771 .with_suggestions(&[
772 "Remove .display() - String implements Display directly",
773 "If this should be a Path, change the type to PathBuf",
774 "Use std::path::Path::new(&string).display()",
775 ])
776 .with_priority(85)
777 .build(),
778 );
779
780 registry.register(
782 FixTemplate::builder(
783 "e0599-ok-readdir",
784 "ok() on ReadDir",
785 ErrorCategory::TypeMismatch,
786 )
787 .with_keywords(&["no method named", "ok", "ReadDir"])
788 .with_explanation(
789 "ReadDir doesn't have an ok() method. It's already a Result that was unwrapped. \
790 The ok() call is redundant.",
791 )
792 .with_suggestions(&[
793 "Remove the .ok() call - the iterator is already available",
794 "If the fs::read_dir() result needs error handling, use ? or match",
795 ])
796 .with_priority(85)
797 .build(),
798 );
799
800 registry.register(
802 FixTemplate::builder(
803 "e0599-duration-since",
804 "duration_since Missing",
805 ErrorCategory::TypeMismatch,
806 )
807 .with_keywords(&["no method named", "duration_since", "DateTime"])
808 .with_explanation(
809 "Python's timedelta arithmetic uses the - operator. \
810 In Rust, use signed_duration_since() or subtract with chrono types.",
811 )
812 .with_suggestions(&[
813 "Use .signed_duration_since(other) for chrono types",
814 "Use datetime1 - datetime2 for Duration",
815 "Check if the type is std::time::Instant vs chrono::DateTime",
816 ])
817 .with_priority(85)
818 .build(),
819 );
820
821 registry.register(
823 FixTemplate::builder(
824 "e0599-toordinal",
825 "toordinal Missing",
826 ErrorCategory::TypeMismatch,
827 )
828 .with_keywords(&["no method named", "toordinal"])
829 .with_explanation(
830 "Python's date.toordinal() returns days since year 1. \
831 In Rust/chrono, use .num_days_from_ce() for similar functionality.",
832 )
833 .with_suggestions(&[
834 "Use .num_days_from_ce() for chrono NaiveDate",
835 "Calculate manually: (date - NaiveDate::from_ymd(1, 1, 1)).num_days()",
836 ])
837 .with_priority(80)
838 .build(),
839 );
840
841 registry.register(
843 FixTemplate::builder(
844 "e0599-date-replace",
845 "date replace Missing",
846 ErrorCategory::TypeMismatch,
847 )
848 .with_keywords(&["no method named", "replace", "Date"])
849 .with_explanation(
850 "Python's date.replace() creates a new date with some fields changed. \
851 In Rust/chrono, use .with_year(), .with_month(), .with_day().",
852 )
853 .with_suggestions(&[
854 "Use .with_year(2024) to change year",
855 "Use .with_month(6) to change month",
856 "Use .with_day(15) to change day",
857 "Chain multiple: date.with_year(2024).with_month(6)",
858 ])
859 .with_priority(80)
860 .build(),
861 );
862
863 registry.register(
867 FixTemplate::builder(
868 "e0308-vec-slice",
869 "Vec to Slice Conversion",
870 ErrorCategory::TypeMismatch,
871 )
872 .with_keywords(&["expected", "&[", "found", "Vec"])
873 .with_explanation(
874 "Function expects a slice &[T] but received Vec<T>. \
875 Use &vec or vec.as_slice() to convert.",
876 )
877 .with_transform(CodeTransform::new(
878 "Convert Vec to slice",
879 r"(\w+)",
880 "&$1",
881 "function(my_vec)",
882 "function(&my_vec)",
883 ))
884 .with_suggestions(&[
885 "Use &vec to borrow as slice",
886 "Use vec.as_slice() for explicit conversion",
887 ])
888 .with_priority(90)
889 .build(),
890 );
891
892 registry.register(
894 FixTemplate::builder(
895 "e0308-arg-count",
896 "Arguments to Function Incorrect",
897 ErrorCategory::TypeMismatch,
898 )
899 .with_keywords(&["arguments to this function are incorrect"])
900 .with_explanation(
901 "The function is being called with wrong number or types of arguments. \
902 Check the function signature and adjust the call.",
903 )
904 .with_suggestions(&[
905 "Check the function signature for expected argument types",
906 "Verify argument order matches the function definition",
907 "Add missing arguments or remove extra ones",
908 ])
909 .with_priority(85)
910 .build(),
911 );
912
913 registry.register(
915 FixTemplate::builder(
916 "e0308-depyler-value",
917 "DepylerValue Type Inference",
918 ErrorCategory::TypeMismatch,
919 )
920 .with_keywords(&["DepylerValue", "expected", "found"])
921 .with_explanation(
922 "DepylerValue is a dynamic type wrapper. The transpiler needs better type \
923 inference to determine the concrete type at compile time.",
924 )
925 .with_suggestions(&[
926 "Add type annotation to help inference",
927 "Use .to_i64(), .to_f64(), .to_string() to extract concrete type",
928 "Check Python source for type hints",
929 ])
930 .with_priority(85)
931 .build(),
932 );
933
934 registry.register(
938 FixTemplate::builder(
939 "e0277-asref-osstr",
940 "AsRef<OsStr> Not Implemented",
941 ErrorCategory::TraitBound,
942 )
943 .with_keywords(&["AsRef<OsStr>", "not implemented", "Value"])
944 .with_explanation(
945 "subprocess/Command functions expect string-like types that implement AsRef<OsStr>. \
946 serde_json::Value doesn't implement this.",
947 )
948 .with_suggestions(&[
949 "Convert to String first: value.as_str().unwrap().to_string()",
950 "Change type inference to Vec<String> instead of Vec<Value>",
951 "Use explicit type annotation: let args: Vec<String>",
952 ])
953 .with_priority(90)
954 .build(),
955 );
956
957 registry.register(
959 FixTemplate::builder(
960 "e0277-iterator",
961 "Iterator Trait Not Implemented",
962 ErrorCategory::TraitBound,
963 )
964 .with_keywords(&["Iterator", "not implemented", "for loop"])
965 .with_explanation(
966 "The for loop requires an Iterator. The type doesn't implement IntoIterator.",
967 )
968 .with_suggestions(&[
969 "Use .iter() for borrowed iteration",
970 "Use .into_iter() for consuming iteration",
971 "Check if the type should be a collection",
972 ])
973 .with_priority(85)
974 .build(),
975 );
976
977 registry.register(
979 FixTemplate::builder(
980 "e0277-display",
981 "Display Trait Not Implemented",
982 ErrorCategory::TraitBound,
983 )
984 .with_keywords(&["Display", "not implemented", "format", "println"])
985 .with_explanation(
986 "The type doesn't implement Display for formatting with {} in format strings.",
987 )
988 .with_suggestions(&[
989 "Use {:?} for Debug formatting instead",
990 "Implement Display trait for the type",
991 "Convert to String first",
992 ])
993 .with_priority(80)
994 .build(),
995 );
996
997 registry.register(
1001 FixTemplate::builder(
1002 "e0609-tuple-field",
1003 "No Named Field on Tuple",
1004 ErrorCategory::TypeMismatch,
1005 )
1006 .with_keywords(&["no field", "tuple", "did you mean"])
1007 .with_explanation(
1008 "Tuples use numeric indices (0, 1, 2), not named fields. \
1009 For named fields, use a struct.",
1010 )
1011 .with_suggestions(&[
1012 "Use tuple.0, tuple.1, etc. for positional access",
1013 "Destructure: let (a, b, c) = tuple;",
1014 "Consider using a struct with named fields",
1015 ])
1016 .with_priority(85)
1017 .build(),
1018 );
1019
1020 registry.register(
1022 FixTemplate::builder(
1023 "e0609-ref-field",
1024 "Field Access on Reference",
1025 ErrorCategory::TypeMismatch,
1026 )
1027 .with_keywords(&["no field", "&", "reference"])
1028 .with_explanation(
1029 "Dereferencing happens automatically for method calls but not for field access. \
1030 The type might be a reference to something without that field.",
1031 )
1032 .with_suggestions(&[
1033 "Dereference explicitly: (*ref).field",
1034 "Check if the base type has the field",
1035 "The reference might be to a different type than expected",
1036 ])
1037 .with_priority(80)
1038 .build(),
1039 );
1040
1041 registry.register(
1045 FixTemplate::builder(
1046 "e0282-cannot-infer",
1047 "Cannot Infer Type",
1048 ErrorCategory::TypeMismatch,
1049 )
1050 .with_keywords(&["cannot infer type", "type annotation needed"])
1051 .with_explanation(
1052 "Rust's type inference couldn't determine the type. Add explicit annotations.",
1053 )
1054 .with_suggestions(&[
1055 "Add type annotation: let x: i32 = ...;",
1056 "Use turbofish syntax: collect::<Vec<_>>()",
1057 "Provide more context with explicit types",
1058 ])
1059 .with_priority(85)
1060 .build(),
1061 );
1062
1063 registry.register(
1065 FixTemplate::builder(
1066 "e0282-collect",
1067 "Collect Type Ambiguous",
1068 ErrorCategory::TypeMismatch,
1069 )
1070 .with_keywords(&["collect", "cannot infer", "type"])
1071 .with_explanation(
1072 "The collect() method can produce many container types. Specify which one.",
1073 )
1074 .with_transform(CodeTransform::new(
1075 "Add turbofish to collect",
1076 r"\.collect\(\)",
1077 ".collect::<Vec<_>>()",
1078 "iter.map(f).collect()",
1079 "iter.map(f).collect::<Vec<_>>()",
1080 ))
1081 .with_suggestions(&[
1082 "Use .collect::<Vec<_>>() for vectors",
1083 "Use .collect::<HashMap<_, _>>() for maps",
1084 "Use .collect::<HashSet<_>>() for sets",
1085 ])
1086 .with_priority(90)
1087 .build(),
1088 );
1089
1090 registry.register(
1094 FixTemplate::builder(
1095 "e0061-arg-count",
1096 "Wrong Number of Arguments",
1097 ErrorCategory::SyntaxError,
1098 )
1099 .with_keywords(&["this function takes", "arguments", "supplied"])
1100 .with_explanation("The function was called with the wrong number of arguments.")
1101 .with_suggestions(&[
1102 "Check the function definition for required arguments",
1103 "Some Python default arguments may need explicit values in Rust",
1104 "Optional parameters might need Option<T> wrapping",
1105 ])
1106 .with_priority(85)
1107 .build(),
1108 );
1109
1110 registry.register(
1114 FixTemplate::builder(
1115 "e0605-non-primitive",
1116 "Non-Primitive Cast",
1117 ErrorCategory::TypeMismatch,
1118 )
1119 .with_keywords(&["non-primitive cast", "as", "cannot cast"])
1120 .with_explanation(
1121 "The `as` keyword only works for primitive types. Use From/Into traits \
1122 or constructor methods for complex types.",
1123 )
1124 .with_suggestions(&[
1125 "Use .into() for types implementing From/Into",
1126 "Use Type::from(value) explicitly",
1127 "Implement From trait for custom conversions",
1128 ])
1129 .with_priority(80)
1130 .build(),
1131 );
1132
1133 registry.register(
1137 FixTemplate::builder(
1138 "e0369-binary-op",
1139 "Binary Operation Not Applicable",
1140 ErrorCategory::TypeMismatch,
1141 )
1142 .with_keywords(&["cannot be applied to type", "binary operation"])
1143 .with_explanation(
1144 "The binary operator (+, -, *, /, etc.) isn't defined for these types. \
1145 Implement the corresponding trait or convert types.",
1146 )
1147 .with_suggestions(&[
1148 "Convert types to match (both i32, both f64, etc.)",
1149 "Use PyOps traits for Python-style operations",
1150 "Implement Add/Sub/Mul/Div traits for custom types",
1151 ])
1152 .with_priority(80)
1153 .build(),
1154 );
1155
1156 registry.register(
1160 FixTemplate::builder(
1161 "e0600-unary-op",
1162 "Unary Operation Not Applicable",
1163 ErrorCategory::TypeMismatch,
1164 )
1165 .with_keywords(&["cannot apply unary operator", "-", "!"])
1166 .with_explanation(
1167 "The unary operator (-, !) isn't defined for this type. \
1168 Check the type and implement Neg or Not traits if needed.",
1169 )
1170 .with_suggestions(&[
1171 "Check if the type is correct for negation",
1172 "Use explicit conversion before applying operator",
1173 "Implement Neg/Not traits for custom types",
1174 ])
1175 .with_priority(75)
1176 .build(),
1177 );
1178
1179 registry.register(
1183 FixTemplate::builder(
1184 "e0425-not-found",
1185 "Name Not Found in Scope",
1186 ErrorCategory::MissingImport,
1187 )
1188 .with_keywords(&["cannot find value", "in this scope"])
1189 .with_explanation("The variable or function is not defined or not in scope.")
1190 .with_suggestions(&[
1191 "Check for typos in the name",
1192 "Import the item with `use`",
1193 "Ensure the variable is defined before use",
1194 ])
1195 .with_priority(85)
1196 .build(),
1197 );
1198
1199 registry.register(
1203 FixTemplate::builder(
1204 "e0599-pyops-missing",
1205 "PyOps Trait Method Missing",
1206 ErrorCategory::TraitBound,
1207 )
1208 .with_keywords(&["no method named", "py_add", "py_sub", "py_mul", "py_div"])
1209 .with_explanation(
1210 "Python-style operations require PyOps traits. The transpiler should \
1211 generate these trait implementations inline.",
1212 )
1213 .with_suggestions(&[
1214 "Check if PyOps traits are included in generated code",
1215 "Verify the types implement the required PyOps trait",
1216 "For Vec operations, ensure element-wise impls exist",
1217 ])
1218 .with_priority(95)
1219 .build(),
1220 );
1221
1222 registry.register(
1224 FixTemplate::builder(
1225 "e0599-json-value",
1226 "serde_json::Value Method Missing",
1227 ErrorCategory::TypeMismatch,
1228 )
1229 .with_keywords(&["no method named", "Value", "serde_json"])
1230 .with_explanation(
1231 "serde_json::Value is a dynamic JSON type. Python dict methods don't \
1232 map directly. Use as_object(), as_array(), etc.",
1233 )
1234 .with_suggestions(&[
1235 "Use .as_object() to get Option<&Map>",
1236 "Use .as_array() to get Option<&Vec>",
1237 "Use .as_str(), .as_i64(), .as_f64() for primitives",
1238 "Use .get(key) for dictionary-like access",
1239 ])
1240 .with_priority(90)
1241 .build(),
1242 );
1243
1244 registry.register(
1246 FixTemplate::builder(
1247 "e0308-time-types",
1248 "Time Type Mismatch",
1249 ErrorCategory::TypeMismatch,
1250 )
1251 .with_keywords(&["std::time", "chrono", "Duration", "Instant"])
1252 .with_explanation(
1253 "Rust has two time libraries: std::time and chrono. They're not interchangeable. \
1254 Python datetime maps better to chrono types.",
1255 )
1256 .with_suggestions(&[
1257 "Use chrono for date/time: NaiveDate, NaiveTime, DateTime",
1258 "Use std::time for durations: Duration, Instant",
1259 "Convert: chrono::Duration::to_std() / from_std()",
1260 ])
1261 .with_priority(85)
1262 .build(),
1263 );
1264
1265 registry.register(
1267 FixTemplate::builder(
1268 "e0308-optional-arg",
1269 "Optional Argument Type Mismatch",
1270 ErrorCategory::TypeMismatch,
1271 )
1272 .with_keywords(&["Option", "expected", "found", "argument"])
1273 .with_explanation(
1274 "Python's optional parameters with defaults need special handling. \
1275 Use Option<T> and provide defaults with .unwrap_or().",
1276 )
1277 .with_suggestions(&[
1278 "Wrap optional params in Option<T>",
1279 "Use .unwrap_or(default) for defaults",
1280 "Consider builder pattern for many optional args",
1281 ])
1282 .with_priority(80)
1283 .build(),
1284 );
1285
1286 registry.register(
1288 FixTemplate::builder(
1289 "e0282-closure",
1290 "Closure Type Cannot Be Inferred",
1291 ErrorCategory::TypeMismatch,
1292 )
1293 .with_keywords(&["closure", "cannot infer", "type"])
1294 .with_explanation(
1295 "Rust closures need type annotations when the type can't be inferred \
1296 from usage context.",
1297 )
1298 .with_suggestions(&[
1299 "Add parameter types: |x: i32| x + 1",
1300 "Add return type: |x| -> i32 { x + 1 }",
1301 "Use the closure immediately to provide context",
1302 ])
1303 .with_priority(75)
1304 .build(),
1305 );
1306
1307 registry.register(
1309 FixTemplate::builder(
1310 "e0308-hashmap-key",
1311 "HashMap Key Type Mismatch",
1312 ErrorCategory::TypeMismatch,
1313 )
1314 .with_keywords(&["HashMap", "expected", "&str", "String", "key"])
1315 .with_explanation(
1316 "HashMap<String, V> needs owned String keys, not &str. \
1317 Use .to_string() when inserting string literals.",
1318 )
1319 .with_transform(CodeTransform::new(
1320 "Convert &str key to String",
1321 r#""([^"]+)""#,
1322 r#""$1".to_string()"#,
1323 r#"map.insert("key", value);"#,
1324 r#"map.insert("key".to_string(), value);"#,
1325 ))
1326 .with_suggestions(&[
1327 "Use .to_string() for literal keys",
1328 "Consider HashMap<&str, V> if all keys are static",
1329 ])
1330 .with_priority(85)
1331 .build(),
1332 );
1333
1334 registry.register(
1336 FixTemplate::builder(
1337 "e0282-vec-new",
1338 "Vec::new() Type Inference",
1339 ErrorCategory::TypeMismatch,
1340 )
1341 .with_keywords(&["Vec::new", "cannot infer", "type"])
1342 .with_explanation(
1343 "Vec::new() without elements can't infer the element type. \
1344 Add a type annotation or use vec![].",
1345 )
1346 .with_suggestions(&[
1347 "Add type: let v: Vec<i32> = Vec::new();",
1348 "Use turbofish: Vec::<i32>::new()",
1349 "Use vec![] with elements if possible",
1350 ])
1351 .with_priority(80)
1352 .build(),
1353 );
1354
1355 registry.register(
1357 FixTemplate::builder(
1358 "e0308-result-error",
1359 "Result Error Type Mismatch",
1360 ErrorCategory::TypeMismatch,
1361 )
1362 .with_keywords(&["Result", "expected", "found", "Err"])
1363 .with_explanation(
1364 "Different Result types have different error types. \
1365 Use .map_err() to convert between error types.",
1366 )
1367 .with_suggestions(&[
1368 "Use .map_err(|e| e.into()) for Into-compatible errors",
1369 "Use anyhow::Result for uniform error handling",
1370 "Consider using thiserror for custom error types",
1371 ])
1372 .with_priority(80)
1373 .build(),
1374 );
1375
1376 registry.register(
1378 FixTemplate::builder(
1379 "e0277-index",
1380 "Index Trait Not Implemented",
1381 ErrorCategory::TraitBound,
1382 )
1383 .with_keywords(&["Index", "not implemented", "cannot index"])
1384 .with_explanation("The type doesn't support indexing with []. Use .get() for safe access.")
1385 .with_suggestions(&[
1386 "Use .get(index) which returns Option<&T>",
1387 "Check if the type should be a Vec or array",
1388 "For HashMap, use .get(&key)",
1389 ])
1390 .with_priority(80)
1391 .build(),
1392 );
1393
1394 registry.register(
1396 FixTemplate::builder(
1397 "e0308-deref",
1398 "Deref Coercion Failure",
1399 ErrorCategory::TypeMismatch,
1400 )
1401 .with_keywords(&["expected", "&", "found", "Box", "Rc", "Arc"])
1402 .with_explanation(
1403 "Smart pointers (Box, Rc, Arc) deref automatically but sometimes need explicit deref.",
1404 )
1405 .with_suggestions(&[
1406 "Use &*ptr to explicitly deref",
1407 "Use .as_ref() for &T from smart pointer",
1408 "Clone if you need an owned value",
1409 ])
1410 .with_priority(75)
1411 .build(),
1412 );
1413
1414 registry.register(
1416 FixTemplate::builder(
1417 "e0106-lifetime",
1418 "Missing Lifetime Specifier",
1419 ErrorCategory::LifetimeError,
1420 )
1421 .with_keywords(&["missing lifetime specifier", "'"])
1422 .with_explanation(
1423 "The compiler can't infer the lifetime. Add explicit lifetime annotations.",
1424 )
1425 .with_suggestions(&[
1426 "Add lifetime parameter: fn foo<'a>(x: &'a str)",
1427 "Consider if you can use owned types instead",
1428 "Use 'static for literals and constants",
1429 ])
1430 .with_priority(80)
1431 .build(),
1432 );
1433}
1434
1435#[cfg(test)]
1436mod tests {
1437 use super::*;
1438
1439 #[test]
1444 fn test_code_transform_creation() {
1445 let transform = CodeTransform::new(
1446 "Test transform",
1447 r"(\w+)",
1448 "$1.clone()",
1449 "let x = value;",
1450 "let x = value.clone();",
1451 );
1452
1453 assert_eq!(transform.description, "Test transform");
1454 assert!(!transform.match_pattern.is_empty());
1455 assert!(!transform.example_before.is_empty());
1456 assert!(!transform.example_after.is_empty());
1457 }
1458
1459 #[test]
1464 fn test_fix_template_builder() {
1465 let template =
1466 FixTemplate::builder("test-id", "Test Template", ErrorCategory::TypeMismatch)
1467 .with_keywords(&["expected", "found"])
1468 .with_explanation("Test explanation")
1469 .with_suggestions(&["Suggestion 1", "Suggestion 2"])
1470 .with_priority(75)
1471 .build();
1472
1473 assert_eq!(template.id, "test-id");
1474 assert_eq!(template.name, "Test Template");
1475 assert_eq!(template.category, ErrorCategory::TypeMismatch);
1476 assert_eq!(template.trigger_keywords.len(), 2);
1477 assert_eq!(template.suggestions.len(), 2);
1478 assert_eq!(template.priority, 75);
1479 }
1480
1481 #[test]
1482 fn test_fix_template_matches() {
1483 let template = FixTemplate::builder("test", "Test", ErrorCategory::TypeMismatch)
1484 .with_keywords(&["expected", "found"])
1485 .build();
1486
1487 assert!(template.matches("error: expected i32, found str"));
1488 assert!(template.matches("EXPECTED TYPE"));
1489 assert!(!template.matches("no keywords here"));
1490 }
1491
1492 #[test]
1493 fn test_fix_template_match_score() {
1494 let template = FixTemplate::builder("test", "Test", ErrorCategory::TypeMismatch)
1495 .with_keywords(&["expected", "found", "type"])
1496 .with_priority(100)
1497 .build();
1498
1499 let score_all = template.match_score("expected type, found other type");
1500 let score_some = template.match_score("expected something");
1501 let score_none = template.match_score("no match");
1502
1503 assert!(score_all > score_some);
1504 assert!(score_some > score_none);
1505 assert!((score_none - 0.0).abs() < 1e-6);
1506 }
1507
1508 #[test]
1509 fn test_fix_template_empty_keywords() {
1510 let template = FixTemplate::builder("test", "Test", ErrorCategory::TypeMismatch).build();
1511
1512 let score = template.match_score("anything");
1513 assert!((score - 0.0).abs() < 1e-6);
1514 }
1515
1516 #[test]
1521 fn test_registry_creation() {
1522 let registry = FixTemplateRegistry::new();
1523 assert_eq!(registry.template_count(), 0);
1524 }
1525
1526 #[test]
1527 fn test_registry_with_defaults() {
1528 let registry = FixTemplateRegistry::with_rust_defaults();
1529 assert!(registry.template_count() > 0);
1530 }
1531
1532 #[test]
1533 fn test_registry_register() {
1534 let mut registry = FixTemplateRegistry::new();
1535
1536 registry
1537 .register(FixTemplate::builder("test", "Test", ErrorCategory::TypeMismatch).build());
1538
1539 assert_eq!(registry.template_count(), 1);
1540 }
1541
1542 #[test]
1543 fn test_registry_get_templates() {
1544 let registry = FixTemplateRegistry::with_rust_defaults();
1545
1546 let type_templates = registry.get_templates(ErrorCategory::TypeMismatch);
1547 assert!(!type_templates.is_empty());
1548
1549 let borrow_templates = registry.get_templates(ErrorCategory::BorrowChecker);
1550 assert!(!borrow_templates.is_empty());
1551 }
1552
1553 #[test]
1554 fn test_registry_find_matches() {
1555 let registry = FixTemplateRegistry::with_rust_defaults();
1556
1557 let matches = registry.find_matches("expected i32, found &str");
1558 assert!(!matches.is_empty());
1559 }
1560
1561 #[test]
1562 fn test_registry_find_best_match() {
1563 let registry = FixTemplateRegistry::with_rust_defaults();
1564
1565 let best = registry.find_best_match("expected String, found &str");
1566 assert!(best.is_some());
1567 }
1568
1569 #[test]
1570 fn test_registry_no_match() {
1571 let registry = FixTemplateRegistry::with_rust_defaults();
1572
1573 let _matches = registry.find_matches("completely unrelated error xyz abc 123");
1574 }
1577
1578 #[test]
1579 fn test_registry_all_templates() {
1580 let registry = FixTemplateRegistry::with_rust_defaults();
1581
1582 let all = registry.all_templates();
1583 assert_eq!(all.len(), registry.template_count());
1584 }
1585
1586 #[test]
1591 fn test_type_mismatch_templates() {
1592 let registry = FixTemplateRegistry::with_rust_defaults();
1593 let templates = registry.get_templates(ErrorCategory::TypeMismatch);
1594
1595 assert!(templates.len() >= 2);
1597
1598 let ids: Vec<&str> = templates.iter().map(|t| t.id.as_str()).collect();
1600 assert!(ids.contains(&"type-int-convert"));
1601 assert!(ids.contains(&"type-string-convert"));
1602 }
1603
1604 #[test]
1605 fn test_borrow_checker_templates() {
1606 let registry = FixTemplateRegistry::with_rust_defaults();
1607 let templates = registry.get_templates(ErrorCategory::BorrowChecker);
1608
1609 assert!(!templates.is_empty());
1610 }
1611
1612 #[test]
1613 fn test_lifetime_templates() {
1614 let registry = FixTemplateRegistry::with_rust_defaults();
1615 let templates = registry.get_templates(ErrorCategory::LifetimeError);
1616
1617 assert!(!templates.is_empty());
1618 }
1619
1620 #[test]
1621 fn test_trait_bound_templates() {
1622 let registry = FixTemplateRegistry::with_rust_defaults();
1623 let templates = registry.get_templates(ErrorCategory::TraitBound);
1624
1625 assert!(!templates.is_empty());
1626 }
1627
1628 #[test]
1629 fn test_import_templates() {
1630 let registry = FixTemplateRegistry::with_rust_defaults();
1631 let templates = registry.get_templates(ErrorCategory::MissingImport);
1632
1633 assert!(!templates.is_empty());
1634 }
1635
1636 #[test]
1637 fn test_syntax_templates() {
1638 let registry = FixTemplateRegistry::with_rust_defaults();
1639 let templates = registry.get_templates(ErrorCategory::SyntaxError);
1640
1641 assert!(!templates.is_empty());
1642 }
1643
1644 #[test]
1649 fn test_string_conversion_match() {
1650 let registry = FixTemplateRegistry::with_rust_defaults();
1651
1652 let matches = registry.find_matches("error: expected `String`, found `&str`");
1653
1654 let has_string_template = matches.iter().any(|t| t.id == "type-string-convert");
1656 assert!(has_string_template);
1657 }
1658
1659 #[test]
1660 fn test_borrow_move_match() {
1661 let registry = FixTemplateRegistry::with_rust_defaults();
1662
1663 let matches = registry.find_matches("cannot move out of borrowed content");
1664
1665 let has_borrow_template = matches.iter().any(|t| t.id == "borrow-move");
1667 assert!(has_borrow_template);
1668 }
1669
1670 #[test]
1671 fn test_template_has_suggestions() {
1672 let registry = FixTemplateRegistry::with_rust_defaults();
1673
1674 for template in registry.all_templates() {
1675 assert!(
1677 !template.suggestions.is_empty(),
1678 "Template {} has no suggestions",
1679 template.id
1680 );
1681 }
1682 }
1683
1684 #[test]
1685 fn test_template_has_explanation() {
1686 let registry = FixTemplateRegistry::with_rust_defaults();
1687
1688 for template in registry.all_templates() {
1689 assert!(
1691 !template.explanation.is_empty(),
1692 "Template {} has no explanation",
1693 template.id
1694 );
1695 }
1696 }
1697}