Skip to main content

verificar/generator/
depyler_patterns.rs

1//! Depyler-targeted pattern generators
2//!
3//! Generates Python programs that exercise problematic transpilation patterns
4//! identified in depyler:
5//! - File/Stdout type unification (File vs Stdout need Box<dyn Write>)
6//! - serde_json::Value misuse
7//! - Method mapping gaps (file.readlines(), iteration)
8//! - Context manager patterns (with open() as f:)
9
10use crate::generator::GeneratedCode;
11use crate::Language;
12
13/// Generates Python programs with file I/O patterns
14#[derive(Debug, Clone)]
15pub struct FileIOPatternGenerator {
16    max_depth: usize,
17}
18
19impl FileIOPatternGenerator {
20    /// Create a new file I/O pattern generator
21    #[must_use]
22    pub fn new(max_depth: usize) -> Self {
23        Self { max_depth }
24    }
25
26    /// Generate all file I/O patterns
27    #[must_use]
28    pub fn generate(&self) -> Vec<GeneratedCode> {
29        let mut programs = Vec::new();
30
31        // Level 1: Basic stdout/file write patterns
32        programs.extend(self.generate_basic_write_patterns());
33
34        // Level 2: File handle passing patterns
35        if self.max_depth >= 2 {
36            programs.extend(self.generate_handle_passing_patterns());
37        }
38
39        // Level 3: Context manager patterns
40        if self.max_depth >= 3 {
41            programs.extend(self.generate_context_manager_patterns());
42        }
43
44        // Level 4: Mixed I/O type patterns (the hardest cases)
45        if self.max_depth >= 4 {
46            programs.extend(self.generate_mixed_io_patterns());
47        }
48
49        programs
50    }
51
52    /// Basic write patterns - stdout vs file
53    fn generate_basic_write_patterns(&self) -> Vec<GeneratedCode> {
54        vec![
55            // Pattern 1: sys.stdout.write()
56            GeneratedCode {
57                code: r#"import sys
58
59def main():
60    sys.stdout.write("hello\n")
61"#
62                .to_string(),
63                language: Language::Python,
64                ast_depth: 1,
65                features: vec!["sys_stdout_write".to_string()],
66            },
67            // Pattern 2: print() function
68            GeneratedCode {
69                code: r#"def main():
70    print("hello")
71"#
72                .to_string(),
73                language: Language::Python,
74                ast_depth: 1,
75                features: vec!["print".to_string()],
76            },
77            // Pattern 3: file.write()
78            GeneratedCode {
79                code: r#"def main():
80    f = open("output.txt", "w")
81    f.write("hello\n")
82    f.close()
83"#
84                .to_string(),
85                language: Language::Python,
86                ast_depth: 1,
87                features: vec!["file_write".to_string()],
88            },
89            // Pattern 4: sys.stderr.write()
90            GeneratedCode {
91                code: r#"import sys
92
93def main():
94    sys.stderr.write("error\n")
95"#
96                .to_string(),
97                language: Language::Python,
98                ast_depth: 1,
99                features: vec!["sys_stderr_write".to_string()],
100            },
101        ]
102    }
103
104    /// Patterns where file handles are passed to functions
105    fn generate_handle_passing_patterns(&self) -> Vec<GeneratedCode> {
106        vec![
107            // Pattern 5: Function accepting file handle
108            GeneratedCode {
109                code: r#"def write_to(f, msg: str):
110    f.write(msg)
111
112def main():
113    f = open("out.txt", "w")
114    write_to(f, "hello\n")
115    f.close()
116"#
117                .to_string(),
118                language: Language::Python,
119                ast_depth: 2,
120                features: vec!["file_handle_param".to_string()],
121            },
122            // Pattern 6: Function accepting stdout
123            GeneratedCode {
124                code: r#"import sys
125
126def write_to(f, msg: str):
127    f.write(msg)
128
129def main():
130    write_to(sys.stdout, "hello\n")
131"#
132                .to_string(),
133                language: Language::Python,
134                ast_depth: 2,
135                features: vec!["stdout_handle_param".to_string()],
136            },
137            // Pattern 7: Function returning file handle
138            GeneratedCode {
139                code: r#"def get_output():
140    return open("out.txt", "w")
141
142def main():
143    f = get_output()
144    f.write("hello\n")
145    f.close()
146"#
147                .to_string(),
148                language: Language::Python,
149                ast_depth: 2,
150                features: vec!["file_handle_return".to_string()],
151            },
152            // Pattern 8: Conditional file vs stdout
153            GeneratedCode {
154                code: r#"import sys
155
156def main():
157    use_file = True
158    if use_file:
159        f = open("out.txt", "w")
160    else:
161        f = sys.stdout
162    f.write("hello\n")
163"#
164                .to_string(),
165                language: Language::Python,
166                ast_depth: 2,
167                features: vec!["conditional_io_type".to_string()],
168            },
169        ]
170    }
171
172    /// Context manager patterns (with statement)
173    fn generate_context_manager_patterns(&self) -> Vec<GeneratedCode> {
174        vec![
175            // Pattern 9: Basic with open
176            GeneratedCode {
177                code: r#"def main():
178    with open("output.txt", "w") as f:
179        f.write("hello\n")
180"#
181                .to_string(),
182                language: Language::Python,
183                ast_depth: 3,
184                features: vec!["context_manager_write".to_string()],
185            },
186            // Pattern 10: with open for reading
187            GeneratedCode {
188                code: r#"def main():
189    with open("input.txt", "r") as f:
190        content = f.read()
191    print(content)
192"#
193                .to_string(),
194                language: Language::Python,
195                ast_depth: 3,
196                features: vec!["context_manager_read".to_string()],
197            },
198            // Pattern 11: Nested with statements
199            GeneratedCode {
200                code: r#"def main():
201    with open("input.txt", "r") as fin:
202        with open("output.txt", "w") as fout:
203            fout.write(fin.read())
204"#
205                .to_string(),
206                language: Language::Python,
207                ast_depth: 3,
208                features: vec!["nested_context_manager".to_string()],
209            },
210            // Pattern 12: Multiple context managers
211            GeneratedCode {
212                code: r#"def main():
213    with open("in.txt", "r") as fin, open("out.txt", "w") as fout:
214        fout.write(fin.read())
215"#
216                .to_string(),
217                language: Language::Python,
218                ast_depth: 3,
219                features: vec!["multiple_context_manager".to_string()],
220            },
221            // Pattern 13: readlines() iteration
222            GeneratedCode {
223                code: r#"def main():
224    with open("input.txt", "r") as f:
225        for line in f.readlines():
226            print(line)
227"#
228                .to_string(),
229                language: Language::Python,
230                ast_depth: 3,
231                features: vec!["readlines_iteration".to_string()],
232            },
233            // Pattern 14: Direct file iteration
234            GeneratedCode {
235                code: r#"def main():
236    with open("input.txt", "r") as f:
237        for line in f:
238            print(line)
239"#
240                .to_string(),
241                language: Language::Python,
242                ast_depth: 3,
243                features: vec!["file_iteration".to_string()],
244            },
245        ]
246    }
247
248    /// Mixed I/O type patterns (most challenging for depyler)
249    fn generate_mixed_io_patterns(&self) -> Vec<GeneratedCode> {
250        vec![
251            // Pattern 15: Function that works with any writable
252            GeneratedCode {
253                code: r#"import sys
254
255def log_message(output, msg: str):
256    output.write(f"[LOG] {msg}\n")
257
258def main():
259    log_message(sys.stdout, "to stdout")
260    with open("log.txt", "w") as f:
261        log_message(f, "to file")
262"#
263                .to_string(),
264                language: Language::Python,
265                ast_depth: 4,
266                features: vec!["polymorphic_writer".to_string()],
267            },
268            // Pattern 16: List of outputs
269            GeneratedCode {
270                code: r#"import sys
271
272def main():
273    outputs = [sys.stdout, open("out.txt", "w")]
274    for out in outputs:
275        out.write("hello\n")
276"#
277                .to_string(),
278                language: Language::Python,
279                ast_depth: 4,
280                features: vec!["writer_list".to_string()],
281            },
282            // Pattern 17: Conditional context manager
283            GeneratedCode {
284                code: r#"import sys
285
286def get_output(use_file: bool):
287    if use_file:
288        return open("out.txt", "w")
289    return sys.stdout
290
291def main():
292    out = get_output(True)
293    out.write("hello\n")
294"#
295                .to_string(),
296                language: Language::Python,
297                ast_depth: 4,
298                features: vec!["conditional_output_factory".to_string()],
299            },
300            // Pattern 18: Streaming filter (like csv_filter example)
301            GeneratedCode {
302                code: r#"import sys
303
304def filter_lines(input_stream, output_stream, predicate):
305    for line in input_stream:
306        if predicate(line):
307            output_stream.write(line)
308
309def main():
310    with open("data.txt", "r") as f:
311        filter_lines(f, sys.stdout, lambda x: len(x) > 5)
312"#
313                .to_string(),
314                language: Language::Python,
315                ast_depth: 4,
316                features: vec!["stream_filter".to_string()],
317            },
318            // Pattern 19: Log analyzer pattern
319            GeneratedCode {
320                code: r#"import sys
321
322def analyze_log(log_file: str):
323    counts = {}
324    with open(log_file, "r") as f:
325        for line in f:
326            level = line.split()[0] if line.strip() else "UNKNOWN"
327            counts[level] = counts.get(level, 0) + 1
328    return counts
329
330def main():
331    result = analyze_log("app.log")
332    for level, count in result.items():
333        sys.stdout.write(f"{level}: {count}\n")
334"#
335                .to_string(),
336                language: Language::Python,
337                ast_depth: 4,
338                features: vec!["log_analyzer".to_string()],
339            },
340            // Pattern 20: I/O streams with type hints
341            GeneratedCode {
342                code: r#"import sys
343from typing import TextIO
344
345def process(input: TextIO, output: TextIO):
346    for line in input:
347        output.write(line.upper())
348
349def main():
350    with open("in.txt", "r") as fin:
351        process(fin, sys.stdout)
352"#
353                .to_string(),
354                language: Language::Python,
355                ast_depth: 4,
356                features: vec!["typed_io_streams".to_string()],
357            },
358        ]
359    }
360}
361
362/// Generates Python programs with JSON/dict patterns
363#[derive(Debug, Clone)]
364pub struct JsonDictPatternGenerator {
365    max_depth: usize,
366}
367
368impl JsonDictPatternGenerator {
369    /// Create a new JSON/dict pattern generator
370    #[must_use]
371    pub fn new(max_depth: usize) -> Self {
372        Self { max_depth }
373    }
374
375    /// Generate all JSON/dict patterns
376    #[must_use]
377    pub fn generate(&self) -> Vec<GeneratedCode> {
378        let mut programs = Vec::new();
379
380        // Level 1: Basic dict operations
381        programs.extend(self.generate_basic_dict_patterns());
382
383        // Level 2: JSON parsing/serialization
384        if self.max_depth >= 2 {
385            programs.extend(self.generate_json_patterns());
386        }
387
388        // Level 3: Nested structures
389        if self.max_depth >= 3 {
390            programs.extend(self.generate_nested_patterns());
391        }
392
393        programs
394    }
395
396    /// Basic dictionary patterns
397    fn generate_basic_dict_patterns(&self) -> Vec<GeneratedCode> {
398        vec![
399            // Pattern 1: Dict literal
400            GeneratedCode {
401                code: r#"def main():
402    d = {"key": "value", "count": 42}
403    print(d["key"])
404"#
405                .to_string(),
406                language: Language::Python,
407                ast_depth: 1,
408                features: vec!["dict_literal".to_string()],
409            },
410            // Pattern 2: Dict get with default
411            GeneratedCode {
412                code: r#"def main():
413    d = {"a": 1}
414    val = d.get("b", 0)
415    print(val)
416"#
417                .to_string(),
418                language: Language::Python,
419                ast_depth: 1,
420                features: vec!["dict_get_default".to_string()],
421            },
422            // Pattern 3: Dict iteration
423            GeneratedCode {
424                code: r#"def main():
425    d = {"a": 1, "b": 2}
426    for k, v in d.items():
427        print(f"{k}: {v}")
428"#
429                .to_string(),
430                language: Language::Python,
431                ast_depth: 1,
432                features: vec!["dict_items_iteration".to_string()],
433            },
434            // Pattern 4: Dict comprehension
435            GeneratedCode {
436                code: r"def main():
437    nums = [1, 2, 3]
438    d = {str(n): n * n for n in nums}
439    print(d)
440"
441                .to_string(),
442                language: Language::Python,
443                ast_depth: 1,
444                features: vec!["dict_comprehension".to_string()],
445            },
446        ]
447    }
448
449    /// JSON parsing and serialization patterns
450    fn generate_json_patterns(&self) -> Vec<GeneratedCode> {
451        vec![
452            // Pattern 5: json.loads
453            GeneratedCode {
454                code: r#"import json
455
456def main():
457    data = json.loads('{"name": "test", "value": 42}')
458    print(data["name"])
459"#
460                .to_string(),
461                language: Language::Python,
462                ast_depth: 2,
463                features: vec!["json_loads".to_string()],
464            },
465            // Pattern 6: json.dumps
466            GeneratedCode {
467                code: r#"import json
468
469def main():
470    data = {"name": "test", "value": 42}
471    output = json.dumps(data)
472    print(output)
473"#
474                .to_string(),
475                language: Language::Python,
476                ast_depth: 2,
477                features: vec!["json_dumps".to_string()],
478            },
479            // Pattern 7: json.load from file
480            GeneratedCode {
481                code: r#"import json
482
483def main():
484    with open("data.json", "r") as f:
485        data = json.load(f)
486    print(data)
487"#
488                .to_string(),
489                language: Language::Python,
490                ast_depth: 2,
491                features: vec!["json_load_file".to_string()],
492            },
493            // Pattern 8: json.dump to file
494            GeneratedCode {
495                code: r#"import json
496
497def main():
498    data = {"name": "test", "value": 42}
499    with open("output.json", "w") as f:
500        json.dump(data, f)
501"#
502                .to_string(),
503                language: Language::Python,
504                ast_depth: 2,
505                features: vec!["json_dump_file".to_string()],
506            },
507        ]
508    }
509
510    /// Nested dict/JSON patterns
511    fn generate_nested_patterns(&self) -> Vec<GeneratedCode> {
512        vec![
513            // Pattern 9: Nested dict access
514            GeneratedCode {
515                code: r#"def main():
516    data = {"user": {"name": "alice", "age": 30}}
517    print(data["user"]["name"])
518"#
519                .to_string(),
520                language: Language::Python,
521                ast_depth: 3,
522                features: vec!["nested_dict_access".to_string()],
523            },
524            // Pattern 10: Dict with list values
525            GeneratedCode {
526                code: r#"def main():
527    data = {"items": [1, 2, 3], "tags": ["a", "b"]}
528    for item in data["items"]:
529        print(item)
530"#
531                .to_string(),
532                language: Language::Python,
533                ast_depth: 3,
534                features: vec!["dict_list_values".to_string()],
535            },
536            // Pattern 11: Dynamic key access
537            GeneratedCode {
538                code: r#"def get_value(d: dict, key: str):
539    return d.get(key)
540
541def main():
542    data = {"a": 1, "b": 2}
543    key = "a"
544    print(get_value(data, key))
545"#
546                .to_string(),
547                language: Language::Python,
548                ast_depth: 3,
549                features: vec!["dynamic_key_access".to_string()],
550            },
551            // Pattern 12: JSON with mixed types (problematic for serde)
552            GeneratedCode {
553                code: r#"import json
554
555def main():
556    data = json.loads('{"str": "hello", "num": 42, "arr": [1, 2], "obj": {"nested": true}}')
557    print(type(data["str"]))
558    print(type(data["num"]))
559    print(type(data["arr"]))
560"#
561                .to_string(),
562                language: Language::Python,
563                ast_depth: 3,
564                features: vec!["json_mixed_types".to_string()],
565            },
566        ]
567    }
568}
569
570/// Generates Python programs with context manager patterns
571#[derive(Debug, Clone)]
572pub struct ContextManagerPatternGenerator {
573    max_depth: usize,
574}
575
576impl ContextManagerPatternGenerator {
577    /// Create a new context manager pattern generator
578    #[must_use]
579    pub fn new(max_depth: usize) -> Self {
580        Self { max_depth }
581    }
582
583    /// Generate all context manager patterns
584    #[must_use]
585    pub fn generate(&self) -> Vec<GeneratedCode> {
586        let mut programs = Vec::new();
587
588        // Level 1: Basic with statements
589        programs.extend(self.generate_basic_with_patterns());
590
591        // Level 2: Custom context managers
592        if self.max_depth >= 2 {
593            programs.extend(self.generate_custom_context_manager_patterns());
594        }
595
596        // Level 3: Exception handling in context
597        if self.max_depth >= 3 {
598            programs.extend(self.generate_exception_patterns());
599        }
600
601        programs
602    }
603
604    /// Basic with statement patterns
605    fn generate_basic_with_patterns(&self) -> Vec<GeneratedCode> {
606        vec![
607            // Pattern 1: Basic file context
608            GeneratedCode {
609                code: r#"def main():
610    with open("test.txt", "w") as f:
611        f.write("hello")
612"#
613                .to_string(),
614                language: Language::Python,
615                ast_depth: 1,
616                features: vec!["basic_file_context".to_string()],
617            },
618            // Pattern 2: Without as clause
619            GeneratedCode {
620                code: r#"from contextlib import suppress
621
622def main():
623    with suppress(FileNotFoundError):
624        with open("missing.txt", "r") as f:
625            print(f.read())
626"#
627                .to_string(),
628                language: Language::Python,
629                ast_depth: 1,
630                features: vec!["suppress_context".to_string()],
631            },
632            // Pattern 3: Multiple items in single with
633            GeneratedCode {
634                code: r#"def main():
635    with open("a.txt", "r") as a, open("b.txt", "w") as b:
636        b.write(a.read())
637"#
638                .to_string(),
639                language: Language::Python,
640                ast_depth: 1,
641                features: vec!["multiple_with_items".to_string()],
642            },
643        ]
644    }
645
646    /// Custom context manager patterns
647    fn generate_custom_context_manager_patterns(&self) -> Vec<GeneratedCode> {
648        vec![
649            // Pattern 4: Class-based context manager
650            GeneratedCode {
651                code: r"class Timer:
652    def __enter__(self):
653        self.start = 0
654        return self
655
656    def __exit__(self, exc_type, exc_val, exc_tb):
657        self.elapsed = 100
658        return False
659
660def main():
661    with Timer() as t:
662        x = 1 + 1
663    print(t.elapsed)
664"
665                .to_string(),
666                language: Language::Python,
667                ast_depth: 2,
668                features: vec!["class_context_manager".to_string()],
669            },
670            // Pattern 5: contextlib.contextmanager decorator
671            GeneratedCode {
672                code: r#"from contextlib import contextmanager
673
674@contextmanager
675def managed_resource():
676    print("acquiring")
677    yield "resource"
678    print("releasing")
679
680def main():
681    with managed_resource() as r:
682        print(r)
683"#
684                .to_string(),
685                language: Language::Python,
686                ast_depth: 2,
687                features: vec!["contextmanager_decorator".to_string()],
688            },
689        ]
690    }
691
692    /// Exception handling in context managers
693    fn generate_exception_patterns(&self) -> Vec<GeneratedCode> {
694        vec![
695            // Pattern 6: Exception in context body
696            GeneratedCode {
697                code: r#"def main():
698    try:
699        with open("test.txt", "w") as f:
700            f.write("hello")
701            raise ValueError("test error")
702    except ValueError as e:
703        print(f"caught: {e}")
704"#
705                .to_string(),
706                language: Language::Python,
707                ast_depth: 3,
708                features: vec!["exception_in_context".to_string()],
709            },
710            // Pattern 7: Context manager that suppresses exceptions
711            GeneratedCode {
712                code: r#"class Suppressor:
713    def __enter__(self):
714        return self
715
716    def __exit__(self, exc_type, exc_val, exc_tb):
717        return True  # Suppress all exceptions
718
719def main():
720    with Suppressor():
721        raise ValueError("this is suppressed")
722    print("continued after exception")
723"#
724                .to_string(),
725                language: Language::Python,
726                ast_depth: 3,
727                features: vec!["exception_suppression".to_string()],
728            },
729        ]
730    }
731}
732
733/// Combined generator for all depyler-problematic patterns
734#[derive(Debug, Clone)]
735pub struct DepylerPatternGenerator {
736    max_depth: usize,
737}
738
739impl DepylerPatternGenerator {
740    /// Create a new combined depyler pattern generator
741    #[must_use]
742    pub fn new(max_depth: usize) -> Self {
743        Self { max_depth }
744    }
745
746    /// Generate all patterns that exercise depyler problem areas
747    #[must_use]
748    pub fn generate(&self) -> Vec<GeneratedCode> {
749        let mut programs = Vec::new();
750
751        // File I/O patterns
752        let file_gen = FileIOPatternGenerator::new(self.max_depth);
753        programs.extend(file_gen.generate());
754
755        // JSON/dict patterns
756        let json_gen = JsonDictPatternGenerator::new(self.max_depth);
757        programs.extend(json_gen.generate());
758
759        // Context manager patterns
760        let ctx_gen = ContextManagerPatternGenerator::new(self.max_depth);
761        programs.extend(ctx_gen.generate());
762
763        programs
764    }
765
766    /// Generate patterns with statistics
767    #[must_use]
768    pub fn generate_with_stats(&self) -> (Vec<GeneratedCode>, DepylerPatternStats) {
769        let file_gen = FileIOPatternGenerator::new(self.max_depth);
770        let file_patterns = file_gen.generate();
771
772        let json_gen = JsonDictPatternGenerator::new(self.max_depth);
773        let json_patterns = json_gen.generate();
774
775        let ctx_gen = ContextManagerPatternGenerator::new(self.max_depth);
776        let ctx_patterns = ctx_gen.generate();
777
778        let stats = DepylerPatternStats {
779            file_io_count: file_patterns.len(),
780            json_dict_count: json_patterns.len(),
781            context_manager_count: ctx_patterns.len(),
782            total_count: file_patterns.len() + json_patterns.len() + ctx_patterns.len(),
783        };
784
785        let mut programs = file_patterns;
786        programs.extend(json_patterns);
787        programs.extend(ctx_patterns);
788
789        (programs, stats)
790    }
791}
792
793/// Statistics about generated depyler patterns
794#[derive(Debug, Clone)]
795pub struct DepylerPatternStats {
796    /// Number of file I/O patterns
797    pub file_io_count: usize,
798    /// Number of JSON/dict patterns
799    pub json_dict_count: usize,
800    /// Number of context manager patterns
801    pub context_manager_count: usize,
802    /// Total pattern count
803    pub total_count: usize,
804}
805
806/// Advanced pattern generator targeting the 4 remaining depyler issues:
807/// 1. Option → Path unwrapping
808/// 2. Box<dyn Write> type unification
809/// 3. serde_json::Value inference for locals
810/// 4. Context manager __enter__() translation
811#[derive(Debug, Clone)]
812pub struct AdvancedDepylerPatternGenerator {
813    max_depth: usize,
814}
815
816impl AdvancedDepylerPatternGenerator {
817    /// Create a new advanced pattern generator
818    #[must_use]
819    pub fn new(max_depth: usize) -> Self {
820        Self { max_depth }
821    }
822
823    /// Generate all advanced patterns
824    #[must_use]
825    pub fn generate(&self) -> Vec<GeneratedCode> {
826        let mut programs = Vec::new();
827
828        // Issue 1: Option → Path patterns
829        programs.extend(self.generate_option_path_patterns());
830
831        // Issue 2: Box<dyn Write> patterns
832        if self.max_depth >= 2 {
833            programs.extend(self.generate_trait_object_patterns());
834        }
835
836        // Issue 3: serde_json::Value local patterns
837        if self.max_depth >= 3 {
838            programs.extend(self.generate_dynamic_value_patterns());
839        }
840
841        // Issue 4: Context manager __enter__ patterns
842        if self.max_depth >= 4 {
843            programs.extend(self.generate_enter_exit_patterns());
844        }
845
846        programs
847    }
848
849    /// Issue 1: Option<String> needs unwrapping before use as path
850    fn generate_option_path_patterns(&self) -> Vec<GeneratedCode> {
851        vec![
852            // Pattern: Optional filename parameter
853            GeneratedCode {
854                code: r#"def process_file(filename: str = None):
855    if filename is None:
856        filename = "default.txt"
857    with open(filename, "r") as f:
858        return f.read()
859
860def main():
861    result = process_file()
862    print(result)
863"#
864                .to_string(),
865                language: Language::Python,
866                ast_depth: 1,
867                features: vec!["option_path_default".to_string()],
868            },
869            // Pattern: Optional from argparse-style
870            GeneratedCode {
871                code: r#"def get_config_path(override: str = None) -> str:
872    if override:
873        return override
874    return "config.json"
875
876def main():
877    path = get_config_path()
878    with open(path, "r") as f:
879        print(f.read())
880"#
881                .to_string(),
882                language: Language::Python,
883                ast_depth: 1,
884                features: vec!["option_path_override".to_string()],
885            },
886            // Pattern: .get() returning optional path
887            GeneratedCode {
888                code: r#"def main():
889    config = {"output": "result.txt"}
890    path = config.get("output", "default.txt")
891    with open(path, "w") as f:
892        f.write("data")
893"#
894                .to_string(),
895                language: Language::Python,
896                ast_depth: 1,
897                features: vec!["option_path_dict_get".to_string()],
898            },
899            // Pattern: or-expression for path fallback
900            GeneratedCode {
901                code: r#"import os
902
903def main():
904    path = os.environ.get("OUTPUT_FILE") or "output.txt"
905    with open(path, "w") as f:
906        f.write("result")
907"#
908                .to_string(),
909                language: Language::Python,
910                ast_depth: 1,
911                features: vec!["option_path_or_fallback".to_string()],
912            },
913        ]
914    }
915
916    /// Issue 2: Box<dyn Write> needed for File vs Stdout unification
917    fn generate_trait_object_patterns(&self) -> Vec<GeneratedCode> {
918        vec![
919            // Pattern: Function returns either File or stdout (CRASH case!)
920            GeneratedCode {
921                code: r#"import sys
922
923def get_writer(use_stdout: bool):
924    if use_stdout:
925        return sys.stdout
926    return open("output.txt", "w")
927
928def main():
929    writer = get_writer(True)
930    writer.write("hello\n")
931"#
932                .to_string(),
933                language: Language::Python,
934                ast_depth: 2,
935                features: vec!["trait_object_conditional_return".to_string()],
936            },
937            // Pattern: Ternary returning different write types
938            GeneratedCode {
939                code: r#"import sys
940
941def main():
942    verbose = True
943    out = sys.stdout if verbose else open("log.txt", "w")
944    out.write("message\n")
945"#
946                .to_string(),
947                language: Language::Python,
948                ast_depth: 2,
949                features: vec!["trait_object_ternary".to_string()],
950            },
951            // Pattern: Function accepting any writable and called with both
952            GeneratedCode {
953                code: r#"import sys
954
955def write_report(output, data: str):
956    output.write(f"Report: {data}\n")
957    output.write("=" * 40 + "\n")
958
959def main():
960    write_report(sys.stdout, "summary")
961    with open("report.txt", "w") as f:
962        write_report(f, "detailed")
963"#
964                .to_string(),
965                language: Language::Python,
966                ast_depth: 2,
967                features: vec!["trait_object_param_both_types".to_string()],
968            },
969            // Pattern: List of mixed writers
970            GeneratedCode {
971                code: r#"import sys
972
973def broadcast(writers: list, msg: str):
974    for w in writers:
975        w.write(msg)
976
977def main():
978    with open("log.txt", "w") as f:
979        broadcast([sys.stdout, f], "broadcast message\n")
980"#
981                .to_string(),
982                language: Language::Python,
983                ast_depth: 2,
984                features: vec!["trait_object_list_mixed".to_string()],
985            },
986            // Pattern: Store writer in variable, use conditionally
987            GeneratedCode {
988                code: r#"import sys
989
990class Logger:
991    def __init__(self, filename: str = None):
992        if filename:
993            self.output = open(filename, "w")
994        else:
995            self.output = sys.stdout
996
997    def log(self, msg: str):
998        self.output.write(f"[LOG] {msg}\n")
999
1000def main():
1001    logger = Logger()
1002    logger.log("test message")
1003"#
1004                .to_string(),
1005                language: Language::Python,
1006                ast_depth: 2,
1007                features: vec!["trait_object_class_field".to_string()],
1008            },
1009        ]
1010    }
1011
1012    /// Issue 3: serde_json::Value inferred for locals that should be typed
1013    fn generate_dynamic_value_patterns(&self) -> Vec<GeneratedCode> {
1014        vec![
1015            // Pattern: Local from json.loads used as dict
1016            GeneratedCode {
1017                code: r#"import json
1018
1019def parse_config(json_str: str) -> str:
1020    config = json.loads(json_str)
1021    name = config["name"]
1022    return name
1023
1024def main():
1025    result = parse_config('{"name": "test"}')
1026    print(result)
1027"#
1028                .to_string(),
1029                language: Language::Python,
1030                ast_depth: 3,
1031                features: vec!["value_json_to_dict_field".to_string()],
1032            },
1033            // Pattern: json value iteration
1034            GeneratedCode {
1035                code: r#"import json
1036
1037def get_items(json_str: str) -> list:
1038    data = json.loads(json_str)
1039    items = data["items"]
1040    return [item["name"] for item in items]
1041
1042def main():
1043    result = get_items('{"items": [{"name": "a"}, {"name": "b"}]}')
1044    print(result)
1045"#
1046                .to_string(),
1047                language: Language::Python,
1048                ast_depth: 3,
1049                features: vec!["value_json_nested_iteration".to_string()],
1050            },
1051            // Pattern: Type narrowing from json
1052            GeneratedCode {
1053                code: r#"import json
1054
1055def process_response(json_str: str):
1056    response = json.loads(json_str)
1057    if response["status"] == "ok":
1058        data = response["data"]
1059        count = data["count"]
1060        return count
1061    return 0
1062
1063def main():
1064    result = process_response('{"status": "ok", "data": {"count": 42}}')
1065    print(result)
1066"#
1067                .to_string(),
1068                language: Language::Python,
1069                ast_depth: 3,
1070                features: vec!["value_json_conditional_access".to_string()],
1071            },
1072            // Pattern: Mixed dict literal and json
1073            GeneratedCode {
1074                code: r#"import json
1075
1076def merge_configs(base: dict, override_json: str) -> dict:
1077    override = json.loads(override_json)
1078    result = base.copy()
1079    result.update(override)
1080    return result
1081
1082def main():
1083    base = {"debug": False, "port": 8080}
1084    merged = merge_configs(base, '{"debug": true}')
1085    print(merged["debug"])
1086"#
1087                .to_string(),
1088                language: Language::Python,
1089                ast_depth: 3,
1090                features: vec!["value_dict_json_merge".to_string()],
1091            },
1092        ]
1093    }
1094
1095    /// Issue 4: Context manager __enter__/__exit__ translation
1096    fn generate_enter_exit_patterns(&self) -> Vec<GeneratedCode> {
1097        vec![
1098            // Pattern: __enter__ returns self
1099            GeneratedCode {
1100                code: r#"class Connection:
1101    def __init__(self, host: str):
1102        self.host = host
1103        self.connected = False
1104
1105    def __enter__(self):
1106        self.connected = True
1107        return self
1108
1109    def __exit__(self, exc_type, exc_val, exc_tb):
1110        self.connected = False
1111        return False
1112
1113    def query(self, sql: str) -> str:
1114        return f"Result from {self.host}"
1115
1116def main():
1117    with Connection("localhost") as conn:
1118        result = conn.query("SELECT 1")
1119        print(result)
1120"#
1121                .to_string(),
1122                language: Language::Python,
1123                ast_depth: 4,
1124                features: vec!["enter_returns_self".to_string()],
1125            },
1126            // Pattern: __enter__ returns different object
1127            GeneratedCode {
1128                code: r#"class FileManager:
1129    def __init__(self, filename: str):
1130        self.filename = filename
1131        self.file = None
1132
1133    def __enter__(self):
1134        self.file = open(self.filename, "w")
1135        return self.file
1136
1137    def __exit__(self, exc_type, exc_val, exc_tb):
1138        if self.file:
1139            self.file.close()
1140        return False
1141
1142def main():
1143    with FileManager("output.txt") as f:
1144        f.write("managed write")
1145"#
1146                .to_string(),
1147                language: Language::Python,
1148                ast_depth: 4,
1149                features: vec!["enter_returns_different".to_string()],
1150            },
1151            // Pattern: Nested context managers with different __enter__ types
1152            GeneratedCode {
1153                code: r#"class Timer:
1154    def __enter__(self):
1155        self.start = 0
1156        return self
1157
1158    def __exit__(self, *args):
1159        self.elapsed = 100
1160        return False
1161
1162def main():
1163    with Timer() as t:
1164        with open("data.txt", "w") as f:
1165            f.write("timed write")
1166    print(f"Elapsed: {t.elapsed}")
1167"#
1168                .to_string(),
1169                language: Language::Python,
1170                ast_depth: 4,
1171                features: vec!["enter_nested_different_types".to_string()],
1172            },
1173            // Pattern: __exit__ with exception handling
1174            GeneratedCode {
1175                code: r#"class TransactionManager:
1176    def __init__(self):
1177        self.committed = False
1178
1179    def __enter__(self):
1180        print("Starting transaction")
1181        return self
1182
1183    def __exit__(self, exc_type, exc_val, exc_tb):
1184        if exc_type is None:
1185            self.committed = True
1186            print("Committed")
1187        else:
1188            print("Rolled back")
1189        return False
1190
1191    def execute(self, query: str):
1192        print(f"Executing: {query}")
1193
1194def main():
1195    with TransactionManager() as tx:
1196        tx.execute("INSERT INTO users VALUES (1)")
1197"#
1198                .to_string(),
1199                language: Language::Python,
1200                ast_depth: 4,
1201                features: vec!["exit_exception_handling".to_string()],
1202            },
1203        ]
1204    }
1205}
1206
1207#[cfg(test)]
1208mod tests {
1209    use super::*;
1210
1211    #[test]
1212    fn test_file_io_generator_basic() {
1213        let gen = FileIOPatternGenerator::new(1);
1214        let programs = gen.generate();
1215        assert_eq!(programs.len(), 4);
1216        assert!(programs.iter().all(|p| p.language == Language::Python));
1217    }
1218
1219    #[test]
1220    fn test_file_io_generator_full_depth() {
1221        let gen = FileIOPatternGenerator::new(4);
1222        let programs = gen.generate();
1223        assert_eq!(programs.len(), 20);
1224
1225        // Check feature coverage
1226        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1227        assert!(features.iter().any(|f| f.contains("stdout")));
1228        assert!(features.iter().any(|f| f.contains("context_manager")));
1229        assert!(features.iter().any(|f| f.contains("polymorphic")));
1230    }
1231
1232    #[test]
1233    fn test_json_dict_generator_basic() {
1234        let gen = JsonDictPatternGenerator::new(1);
1235        let programs = gen.generate();
1236        assert_eq!(programs.len(), 4);
1237    }
1238
1239    #[test]
1240    fn test_json_dict_generator_full_depth() {
1241        let gen = JsonDictPatternGenerator::new(3);
1242        let programs = gen.generate();
1243        assert_eq!(programs.len(), 12);
1244
1245        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1246        assert!(features.iter().any(|f| f.contains("json_loads")));
1247        assert!(features.iter().any(|f| f.contains("nested")));
1248    }
1249
1250    #[test]
1251    fn test_context_manager_generator_basic() {
1252        let gen = ContextManagerPatternGenerator::new(1);
1253        let programs = gen.generate();
1254        assert_eq!(programs.len(), 3);
1255    }
1256
1257    #[test]
1258    fn test_context_manager_generator_full_depth() {
1259        let gen = ContextManagerPatternGenerator::new(3);
1260        let programs = gen.generate();
1261        assert_eq!(programs.len(), 7);
1262    }
1263
1264    #[test]
1265    fn test_combined_generator() {
1266        let gen = DepylerPatternGenerator::new(4);
1267        let programs = gen.generate();
1268        assert_eq!(programs.len(), 39); // 20 + 12 + 7
1269    }
1270
1271    #[test]
1272    fn test_combined_generator_with_stats() {
1273        let gen = DepylerPatternGenerator::new(4);
1274        let (programs, stats) = gen.generate_with_stats();
1275
1276        assert_eq!(stats.file_io_count, 20);
1277        assert_eq!(stats.json_dict_count, 12);
1278        assert_eq!(stats.context_manager_count, 7);
1279        assert_eq!(stats.total_count, 39);
1280        assert_eq!(programs.len(), stats.total_count);
1281    }
1282
1283    #[test]
1284    fn test_patterns_have_valid_python_syntax() {
1285        let gen = DepylerPatternGenerator::new(4);
1286        let programs = gen.generate();
1287
1288        // All patterns should have def main(): or class definition
1289        for prog in &programs {
1290            assert!(
1291                prog.code.contains("def main()") || prog.code.contains("class "),
1292                "Pattern missing main function: {}",
1293                prog.code
1294            );
1295        }
1296    }
1297
1298    #[test]
1299    fn test_file_io_features_are_tagged() {
1300        let gen = FileIOPatternGenerator::new(4);
1301        let programs = gen.generate();
1302
1303        // All programs should have at least one feature tag
1304        for prog in &programs {
1305            assert!(
1306                !prog.features.is_empty(),
1307                "Pattern missing features: {}",
1308                prog.code
1309            );
1310        }
1311    }
1312
1313    #[test]
1314    fn test_depth_constraint_respected() {
1315        // Depth 1 should only give basic patterns
1316        let gen1 = FileIOPatternGenerator::new(1);
1317        let programs1 = gen1.generate();
1318        assert!(programs1.iter().all(|p| p.ast_depth <= 1));
1319
1320        // Depth 4 should include deep patterns
1321        let gen4 = FileIOPatternGenerator::new(4);
1322        let programs4 = gen4.generate();
1323        assert!(programs4.iter().any(|p| p.ast_depth == 4));
1324    }
1325
1326    // Tests for AdvancedDepylerPatternGenerator
1327
1328    #[test]
1329    fn test_advanced_generator_option_path() {
1330        let gen = AdvancedDepylerPatternGenerator::new(1);
1331        let programs = gen.generate();
1332        assert_eq!(programs.len(), 4); // 4 option/path patterns
1333
1334        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1335        assert!(features.iter().any(|f| f.contains("option_path")));
1336    }
1337
1338    #[test]
1339    fn test_advanced_generator_trait_objects() {
1340        let gen = AdvancedDepylerPatternGenerator::new(2);
1341        let programs = gen.generate();
1342        assert_eq!(programs.len(), 9); // 4 option + 5 trait object
1343
1344        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1345        assert!(features.iter().any(|f| f.contains("trait_object")));
1346    }
1347
1348    #[test]
1349    fn test_advanced_generator_json_value() {
1350        let gen = AdvancedDepylerPatternGenerator::new(3);
1351        let programs = gen.generate();
1352        assert_eq!(programs.len(), 13); // 4 + 5 + 4 json value
1353
1354        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1355        assert!(features.iter().any(|f| f.contains("value_json")));
1356    }
1357
1358    #[test]
1359    fn test_advanced_generator_full_depth() {
1360        let gen = AdvancedDepylerPatternGenerator::new(4);
1361        let programs = gen.generate();
1362        assert_eq!(programs.len(), 17); // 4 + 5 + 4 + 4 context manager
1363
1364        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).collect();
1365        assert!(features.iter().any(|f| f.contains("enter_")));
1366    }
1367
1368    #[test]
1369    fn test_advanced_patterns_target_depyler_issues() {
1370        let gen = AdvancedDepylerPatternGenerator::new(4);
1371        let programs = gen.generate();
1372
1373        // Should have patterns for all 4 issue categories
1374        let features: Vec<_> = programs.iter().flat_map(|p| &p.features).cloned().collect();
1375
1376        // Issue 1: Option → Path
1377        assert!(features.iter().any(|f| f.contains("option_path")));
1378        // Issue 2: Box<dyn Write>
1379        assert!(features.iter().any(|f| f.contains("trait_object")));
1380        // Issue 3: serde_json::Value
1381        assert!(features.iter().any(|f| f.contains("value_json")));
1382        // Issue 4: __enter__/__exit__
1383        assert!(features.iter().any(|f| f.contains("enter_")));
1384    }
1385
1386    #[test]
1387    fn test_advanced_patterns_have_main() {
1388        let gen = AdvancedDepylerPatternGenerator::new(4);
1389        let programs = gen.generate();
1390
1391        for prog in &programs {
1392            assert!(
1393                prog.code.contains("def main()") || prog.code.contains("class "),
1394                "Pattern missing main function or class: {}",
1395                prog.features.first().unwrap_or(&"unknown".to_string())
1396            );
1397        }
1398    }
1399}