Skip to main content

portalis_transpiler/
lifetime_analysis.rs

1//! Lifetime Analysis and Insertion
2//!
3//! Analyzes Python code patterns and inserts appropriate Rust lifetime annotations
4//! for references, ensuring memory safety and preventing dangling references.
5
6use std::collections::{HashMap, HashSet};
7
8/// Represents a Rust lifetime
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct Lifetime {
11    pub name: String,
12}
13
14impl Lifetime {
15    pub fn new(name: impl Into<String>) -> Self {
16        Self { name: name.into() }
17    }
18
19    pub fn anonymous() -> Self {
20        Self::new("_")
21    }
22
23    pub fn static_lifetime() -> Self {
24        Self::new("static")
25    }
26
27    pub fn to_string(&self) -> String {
28        format!("'{}", self.name)
29    }
30}
31
32/// Type with optional lifetime annotations
33#[derive(Debug, Clone)]
34pub struct TypeWithLifetime {
35    pub base_type: String,
36    pub lifetimes: Vec<Lifetime>,
37    pub is_reference: bool,
38    pub is_mutable: bool,
39}
40
41impl TypeWithLifetime {
42    pub fn new(base_type: impl Into<String>) -> Self {
43        Self {
44            base_type: base_type.into(),
45            lifetimes: vec![],
46            is_reference: false,
47            is_mutable: false,
48        }
49    }
50
51    pub fn with_lifetime(mut self, lifetime: Lifetime) -> Self {
52        self.lifetimes.push(lifetime);
53        self
54    }
55
56    pub fn as_reference(mut self) -> Self {
57        self.is_reference = true;
58        self
59    }
60
61    pub fn as_mut_reference(mut self) -> Self {
62        self.is_reference = true;
63        self.is_mutable = true;
64        self
65    }
66
67    pub fn to_rust_string(&self) -> String {
68        let mut result = String::new();
69
70        if self.is_reference {
71            result.push('&');
72            if !self.lifetimes.is_empty() {
73                result.push_str(&self.lifetimes[0].to_string());
74                result.push(' ');
75            }
76            if self.is_mutable {
77                result.push_str("mut ");
78            }
79        }
80
81        result.push_str(&self.base_type);
82
83        // Generic lifetime parameters
84        if !self.is_reference && !self.lifetimes.is_empty() {
85            result.push('<');
86            let lifetime_strs: Vec<_> = self.lifetimes.iter().map(|l| l.to_string()).collect();
87            result.push_str(&lifetime_strs.join(", "));
88            result.push('>');
89        }
90
91        result
92    }
93}
94
95/// Lifetime constraint between lifetimes
96#[derive(Debug, Clone)]
97pub enum LifetimeConstraint {
98    /// 'a outlives 'b ('a: 'b)
99    Outlives(Lifetime, Lifetime),
100    /// 'a is the same as 'b
101    Equal(Lifetime, Lifetime),
102    /// 'a must be 'static
103    Static(Lifetime),
104}
105
106/// Function signature with lifetime annotations
107#[derive(Debug, Clone)]
108pub struct FunctionSignature {
109    pub name: String,
110    pub lifetime_params: Vec<Lifetime>,
111    pub params: Vec<(String, TypeWithLifetime)>,
112    pub return_type: Option<TypeWithLifetime>,
113    pub constraints: Vec<LifetimeConstraint>,
114}
115
116impl FunctionSignature {
117    pub fn new(name: impl Into<String>) -> Self {
118        Self {
119            name: name.into(),
120            lifetime_params: vec![],
121            params: vec![],
122            return_type: None,
123            constraints: vec![],
124        }
125    }
126
127    pub fn add_lifetime_param(&mut self, lifetime: Lifetime) {
128        if !self.lifetime_params.contains(&lifetime) {
129            self.lifetime_params.push(lifetime);
130        }
131    }
132
133    pub fn add_param(&mut self, name: String, ty: TypeWithLifetime) {
134        // Collect lifetimes from parameter type
135        for lifetime in &ty.lifetimes {
136            self.add_lifetime_param(lifetime.clone());
137        }
138        self.params.push((name, ty));
139    }
140
141    pub fn set_return_type(&mut self, ty: TypeWithLifetime) {
142        // Collect lifetimes from return type
143        for lifetime in &ty.lifetimes {
144            self.add_lifetime_param(lifetime.clone());
145        }
146        self.return_type = Some(ty);
147    }
148
149    pub fn to_rust_string(&self) -> String {
150        let mut result = format!("fn {}", self.name);
151
152        // Lifetime parameters
153        if !self.lifetime_params.is_empty() {
154            result.push('<');
155            let lifetime_strs: Vec<_> = self
156                .lifetime_params
157                .iter()
158                .map(|l| l.to_string())
159                .collect();
160            result.push_str(&lifetime_strs.join(", "));
161            result.push('>');
162        }
163
164        // Function parameters
165        result.push('(');
166        let param_strs: Vec<_> = self
167            .params
168            .iter()
169            .map(|(name, ty)| format!("{}: {}", name, ty.to_rust_string()))
170            .collect();
171        result.push_str(&param_strs.join(", "));
172        result.push(')');
173
174        // Return type
175        if let Some(ret_ty) = &self.return_type {
176            result.push_str(" -> ");
177            result.push_str(&ret_ty.to_rust_string());
178        }
179
180        result
181    }
182}
183
184/// Struct definition with lifetime parameters
185#[derive(Debug, Clone)]
186pub struct StructDefinition {
187    pub name: String,
188    pub lifetime_params: Vec<Lifetime>,
189    pub fields: Vec<(String, TypeWithLifetime)>,
190}
191
192impl StructDefinition {
193    pub fn new(name: impl Into<String>) -> Self {
194        Self {
195            name: name.into(),
196            lifetime_params: vec![],
197            fields: vec![],
198        }
199    }
200
201    pub fn add_field(&mut self, name: String, ty: TypeWithLifetime) {
202        // Collect lifetimes from field type
203        for lifetime in &ty.lifetimes {
204            if !self.lifetime_params.contains(lifetime) {
205                self.lifetime_params.push(lifetime.clone());
206            }
207        }
208        self.fields.push((name, ty));
209    }
210
211    pub fn to_rust_string(&self) -> String {
212        let mut result = format!("struct {}", self.name);
213
214        // Lifetime parameters
215        if !self.lifetime_params.is_empty() {
216            result.push('<');
217            let lifetime_strs: Vec<_> = self
218                .lifetime_params
219                .iter()
220                .map(|l| l.to_string())
221                .collect();
222            result.push_str(&lifetime_strs.join(", "));
223            result.push('>');
224        }
225
226        result.push_str(" {\n");
227
228        // Fields
229        for (name, ty) in &self.fields {
230            result.push_str(&format!("    {}: {},\n", name, ty.to_rust_string()));
231        }
232
233        result.push('}');
234        result
235    }
236}
237
238/// Lifetime analyzer
239pub struct LifetimeAnalyzer {
240    /// Counter for generating fresh lifetime names
241    lifetime_counter: usize,
242    /// Current scope's lifetime information
243    scope_lifetimes: HashMap<String, Lifetime>,
244    /// Detected reference patterns
245    reference_patterns: Vec<ReferencePattern>,
246}
247
248/// Reference pattern detected in code
249#[derive(Debug, Clone)]
250pub enum ReferencePattern {
251    /// Borrowing a variable
252    Borrow { var: String, is_mutable: bool },
253    /// Returning a reference from a function
254    ReturnRef { param: String },
255    /// Storing a reference in a struct
256    StructRef { field: String, source: String },
257    /// Reference in a collection
258    CollectionRef { container: String, element: String },
259}
260
261impl LifetimeAnalyzer {
262    pub fn new() -> Self {
263        Self {
264            lifetime_counter: 0,
265            scope_lifetimes: HashMap::new(),
266            reference_patterns: Vec::new(),
267        }
268    }
269
270    /// Generate a fresh lifetime
271    pub fn fresh_lifetime(&mut self) -> Lifetime {
272        let name = format!("a{}", self.lifetime_counter);
273        self.lifetime_counter += 1;
274        Lifetime::new(name)
275    }
276
277    /// Analyze function and determine lifetime annotations
278    pub fn analyze_function(
279        &mut self,
280        params: &[(String, String, bool)], // (name, type, is_reference)
281        return_type: Option<(&str, bool)>,  // (type, is_reference)
282        returns_param: Option<&str>,        // which param is returned
283    ) -> FunctionSignature {
284        let mut sig = FunctionSignature::new("function");
285
286        // Apply elision rules first
287        if let Some(elided) = self.try_elision(params, return_type, returns_param) {
288            return elided;
289        }
290
291        // Assign lifetimes to reference parameters
292        let mut param_lifetimes: HashMap<String, Lifetime> = HashMap::new();
293
294        for (name, ty, is_ref) in params {
295            let param_ty = if *is_ref {
296                let lifetime = self.fresh_lifetime();
297                param_lifetimes.insert(name.clone(), lifetime.clone());
298                TypeWithLifetime::new(ty.clone())
299                    .as_reference()
300                    .with_lifetime(lifetime)
301            } else {
302                TypeWithLifetime::new(ty.clone())
303            };
304
305            sig.add_param(name.clone(), param_ty);
306        }
307
308        // Handle return type
309        if let Some((ret_ty, is_ref)) = return_type {
310            if is_ref {
311                // If returning a reference, it must come from a parameter
312                if let Some(param_name) = returns_param {
313                    if let Some(lifetime) = param_lifetimes.get(param_name) {
314                        let return_ty = TypeWithLifetime::new(ret_ty)
315                            .as_reference()
316                            .with_lifetime(lifetime.clone());
317                        sig.set_return_type(return_ty);
318                    }
319                } else {
320                    // Generic lifetime for return
321                    let lifetime = self.fresh_lifetime();
322                    let return_ty = TypeWithLifetime::new(ret_ty)
323                        .as_reference()
324                        .with_lifetime(lifetime);
325                    sig.set_return_type(return_ty);
326                }
327            } else {
328                sig.set_return_type(TypeWithLifetime::new(ret_ty));
329            }
330        }
331
332        sig
333    }
334
335    /// Try to apply lifetime elision rules
336    fn try_elision(
337        &mut self,
338        params: &[(String, String, bool)],
339        return_type: Option<(&str, bool)>,
340        returns_param: Option<&str>,
341    ) -> Option<FunctionSignature> {
342        let ref_params: Vec<_> = params.iter().filter(|(_, _, is_ref)| *is_ref).collect();
343
344        // Rule 1: Each elided lifetime in function arguments gets a distinct parameter
345        // Rule 2: If there's exactly one input lifetime, it's assigned to all output lifetimes
346        // Rule 3: If there are multiple input lifetimes and one is &self or &mut self, the lifetime of self is assigned to all output lifetimes
347
348        if ref_params.is_empty() {
349            // No references, no lifetimes needed
350            let mut sig = FunctionSignature::new("function");
351            for (name, ty, _) in params {
352                sig.add_param(name.clone(), TypeWithLifetime::new(ty.clone()));
353            }
354            if let Some((ret_ty, _)) = return_type {
355                sig.set_return_type(TypeWithLifetime::new(ret_ty));
356            }
357            return Some(sig);
358        }
359
360        if ref_params.len() == 1 {
361            // Single reference parameter - elision applies
362            if let Some((ret_ty, true)) = return_type {
363                let mut sig = FunctionSignature::new("function");
364
365                // Use elided lifetime (not explicitly written)
366                for (name, ty, is_ref) in params {
367                    if *is_ref {
368                        sig.add_param(name.clone(), TypeWithLifetime::new(ty.clone()).as_reference());
369                    } else {
370                        sig.add_param(name.clone(), TypeWithLifetime::new(ty.clone()));
371                    }
372                }
373
374                sig.set_return_type(TypeWithLifetime::new(ret_ty).as_reference());
375                return Some(sig);
376            }
377        }
378
379        None
380    }
381
382    /// Analyze struct and determine lifetime parameters
383    pub fn analyze_struct(&mut self, fields: &[(String, String, bool)]) -> StructDefinition {
384        let mut struct_def = StructDefinition::new("Struct");
385
386        // Fields with references need lifetime parameters
387        let mut has_references = false;
388        let lifetime = self.fresh_lifetime();
389
390        for (name, ty, is_ref) in fields {
391            let field_ty = if *is_ref {
392                has_references = true;
393                TypeWithLifetime::new(ty.clone())
394                    .as_reference()
395                    .with_lifetime(lifetime.clone())
396            } else {
397                TypeWithLifetime::new(ty.clone())
398            };
399
400            struct_def.add_field(name.clone(), field_ty);
401        }
402
403        struct_def
404    }
405
406    /// Generate lifetime bounds for trait implementations
407    pub fn generate_trait_bounds(&self, lifetimes: &[Lifetime]) -> Vec<String> {
408        let mut bounds = Vec::new();
409
410        for lifetime in lifetimes {
411            // Common bound: 'a: 'static (lifetime outlives 'static)
412            // Or 'a: 'b (one lifetime outlives another)
413            bounds.push(format!("{}: 'static", lifetime.to_string()));
414        }
415
416        bounds
417    }
418
419    /// Detect common lifetime patterns
420    pub fn detect_pattern(&mut self, pattern: ReferencePattern) {
421        self.reference_patterns.push(pattern);
422    }
423
424    /// Generate lifetime annotations for detected patterns
425    pub fn generate_annotations(&self) -> Vec<String> {
426        let mut annotations = Vec::new();
427
428        for pattern in &self.reference_patterns {
429            match pattern {
430                ReferencePattern::Borrow { var, is_mutable } => {
431                    let mutability = if *is_mutable { "mut " } else { "" };
432                    annotations.push(format!("&{}{}", mutability, var));
433                }
434                ReferencePattern::ReturnRef { param } => {
435                    annotations.push(format!("// Returns reference to {}", param));
436                }
437                ReferencePattern::StructRef { field, source } => {
438                    annotations.push(format!("// {} references {}", field, source));
439                }
440                ReferencePattern::CollectionRef { container, element } => {
441                    annotations.push(format!("// {} contains references to {}", container, element));
442                }
443            }
444        }
445
446        annotations
447    }
448}
449
450impl Default for LifetimeAnalyzer {
451    fn default() -> Self {
452        Self::new()
453    }
454}
455
456/// Common lifetime patterns and their translations
457pub struct LifetimePatterns;
458
459impl LifetimePatterns {
460    /// Pattern: Returning a reference to a parameter
461    pub fn return_param_ref() -> String {
462        r#"fn first<'a>(x: &'a str, y: &str) -> &'a str {
463    x
464}"#
465        .to_string()
466    }
467
468    /// Pattern: Struct with reference field
469    pub fn struct_with_ref() -> String {
470        r#"struct Container<'a> {
471    data: &'a str,
472}"#
473        .to_string()
474    }
475
476    /// Pattern: Multiple lifetimes
477    pub fn multiple_lifetimes() -> String {
478        r#"fn combine<'a, 'b>(x: &'a str, y: &'b str) -> String {
479    format!("{}{}", x, y)
480}"#
481        .to_string()
482    }
483
484    /// Pattern: Lifetime bounds
485    pub fn lifetime_bounds() -> String {
486        r#"struct Wrapper<'a, T: 'a> {
487    data: &'a T,
488}"#
489        .to_string()
490    }
491
492    /// Pattern: Static lifetime
493    pub fn static_lifetime() -> String {
494        r#"const MESSAGE: &'static str = "Hello, World!";
495
496fn get_message() -> &'static str {
497    MESSAGE
498}"#
499        .to_string()
500    }
501
502    /// Pattern: Lifetime elision (implicit)
503    pub fn elision() -> String {
504        r#"// With elision (no explicit lifetimes needed):
505fn process(s: &str) -> &str {
506    s.trim()
507}
508
509// Equivalent to:
510fn process_explicit<'a>(s: &'a str) -> &'a str {
511    s.trim()
512}"#
513        .to_string()
514    }
515
516    /// Pattern: Self lifetime
517    pub fn self_lifetime() -> String {
518        r#"impl<'a> Container<'a> {
519    fn get_data(&self) -> &'a str {
520        self.data
521    }
522}"#
523        .to_string()
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530
531    #[test]
532    fn test_lifetime_creation() {
533        let lifetime = Lifetime::new("a");
534        assert_eq!(lifetime.to_string(), "'a");
535    }
536
537    #[test]
538    fn test_type_with_lifetime() {
539        let ty = TypeWithLifetime::new("str")
540            .as_reference()
541            .with_lifetime(Lifetime::new("a"));
542
543        assert_eq!(ty.to_rust_string(), "&'a str");
544    }
545
546    #[test]
547    fn test_function_signature() {
548        let mut sig = FunctionSignature::new("example");
549        sig.add_param(
550            "x".to_string(),
551            TypeWithLifetime::new("str")
552                .as_reference()
553                .with_lifetime(Lifetime::new("a")),
554        );
555        sig.set_return_type(
556            TypeWithLifetime::new("str")
557                .as_reference()
558                .with_lifetime(Lifetime::new("a")),
559        );
560
561        let rust = sig.to_rust_string();
562        assert!(rust.contains("fn example"));
563        assert!(rust.contains("'a"));
564    }
565
566    #[test]
567    fn test_elision_single_param() {
568        let mut analyzer = LifetimeAnalyzer::new();
569        let sig = analyzer.analyze_function(
570            &[("s".to_string(), "str".to_string(), true)],
571            Some(("str", true)),
572            Some("s"),
573        );
574
575        // Should use elision (no explicit lifetime in signature)
576        let rust = sig.to_rust_string();
577        assert!(rust.contains("&str"));
578    }
579
580    #[test]
581    fn test_struct_with_references() {
582        let mut analyzer = LifetimeAnalyzer::new();
583        let struct_def = analyzer.analyze_struct(&[
584            ("name".to_string(), "str".to_string(), true),
585            ("age".to_string(), "i32".to_string(), false),
586        ]);
587
588        let rust = struct_def.to_rust_string();
589        assert!(rust.contains("'a"));
590        assert!(rust.contains("&'a str"));
591    }
592}