rusty_cpp/parser/
annotations.rs

1use clang::Entity;
2use regex::Regex;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum LifetimeAnnotation {
6    // 'a, 'b, etc - just the lifetime name
7    Lifetime(String),
8    // &'a T - immutable reference with lifetime
9    Ref(String),
10    // &'a mut T - mutable reference with lifetime  
11    MutRef(String),
12    // owned - for ownership transfer
13    Owned,
14}
15
16impl LifetimeAnnotation {
17    pub fn is_owned(&self) -> bool {
18        matches!(self, LifetimeAnnotation::Owned)
19    }
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum SafetyAnnotation {
24    Safe,    // @safe - enforce borrow checking
25    Unsafe,  // @unsafe - skip borrow checking
26}
27
28#[derive(Debug, Clone)]
29pub struct FunctionSignature {
30    pub name: String,
31    pub return_lifetime: Option<LifetimeAnnotation>,
32    pub param_lifetimes: Vec<Option<LifetimeAnnotation>>,
33    pub lifetime_bounds: Vec<LifetimeBound>, // e.g., 'a: 'b
34    pub safety: Option<SafetyAnnotation>, // @safe or @unsafe
35}
36
37#[derive(Debug, Clone)]
38pub struct LifetimeBound {
39    pub longer: String,  // 'a in 'a: 'b
40    pub shorter: String, // 'b in 'a: 'b
41}
42
43pub fn extract_annotations(entity: &Entity) -> Option<FunctionSignature> {
44    let name = entity.get_name()?;
45
46    // Try getting comment from LibClang first (doc comments like /// or /** */)
47    if let Some(comment) = entity.get_comment() {
48        if let Some(sig) = parse_lifetime_annotations(&comment, name.clone()) {
49            return Some(sig);
50        }
51        // Comment exists but no lifetime annotation found, fall through to source reading
52    }
53
54    // If no doc comment, read source file for // @lifetime: annotations
55    // (similar to how we detect // @unsafe blocks)
56    if let Some(sig) = read_lifetime_from_source(entity, &name) {
57        return Some(sig);
58    }
59
60    None
61}
62
63/// Read lifetime annotations from source file (for // comments that LibClang doesn't capture)
64fn read_lifetime_from_source(entity: &Entity, name: &str) -> Option<FunctionSignature> {
65    use std::fs::File;
66    use std::io::{BufRead, BufReader};
67
68    let location = entity.get_location()?;
69    let file_location = location.get_file_location();
70    let file = file_location.file?;
71    let file_path = file.get_path();
72    let entity_line = file_location.line as usize;
73
74    // Read the source file
75    let file_handle = File::open(&file_path).ok()?;
76    let reader = BufReader::new(file_handle);
77
78    // Look for annotations in the lines before the entity
79    let mut annotations = String::new();
80    let mut current_line = 0;
81
82    for line_result in reader.lines() {
83        current_line += 1;
84        let line = line_result.ok()?;
85
86        // Check if we're at or past the entity line
87        if current_line >= entity_line {
88            // Parse accumulated annotations
89            if !annotations.is_empty() {
90                if let Some(sig) = parse_lifetime_annotations(&annotations, name.to_string()) {
91                    return Some(sig);
92                }
93            }
94            return None;
95        }
96
97        // Accumulate comment lines before the entity
98        let trimmed = line.trim();
99        if trimmed.starts_with("//") {
100            annotations.push_str(&line);
101            annotations.push('\n');
102        } else if !trimmed.is_empty() && !trimmed.starts_with("/*") {
103            // Non-comment, non-empty line - reset accumulation
104            annotations.clear();
105        }
106    }
107
108    None
109}
110
111// Parse annotations like:
112// @lifetime: 'a -> &'a T
113// @lifetime: ('a, 'b) -> &'a T where 'a: 'b
114// @lifetime: owned
115fn parse_lifetime_annotations(comment: &str, func_name: String) -> Option<FunctionSignature> {
116    // Look for @safe or @unsafe annotation first
117    let safe_re = Regex::new(r"@safe\b").ok()?;
118    let unsafe_re = Regex::new(r"@unsafe\b").ok()?;
119
120    let safety = if safe_re.is_match(comment) {
121        Some(SafetyAnnotation::Safe)
122    } else if unsafe_re.is_match(comment) {
123        Some(SafetyAnnotation::Unsafe)
124    } else {
125        None
126    };
127
128    // Look for @lifetime annotation
129    let lifetime_re = Regex::new(r"@lifetime:\s*(.+)").ok()?;
130
131    // If we have either safety or lifetime annotations, create a signature
132    if let Some(captures) = lifetime_re.captures(comment) {
133        let annotation_str = captures.get(1)?.as_str();
134
135        // Parse the annotation string
136        let mut signature = FunctionSignature {
137            name: func_name.clone(),
138            return_lifetime: None,
139            param_lifetimes: Vec::new(),
140            lifetime_bounds: Vec::new(),
141            safety,
142        };
143
144        // Check for where clause
145        let parts: Vec<&str> = annotation_str.split("where").collect();
146        let main_part = parts[0].trim();
147
148        // Parse lifetime bounds from where clause
149        if parts.len() > 1 {
150            let bounds_str = parts[1].trim();
151            signature.lifetime_bounds = parse_lifetime_bounds(bounds_str);
152        }
153
154        // Parse main lifetime specification
155        if main_part.contains("->") {
156            // Has parameters and return type
157            let arrow_parts: Vec<&str> = main_part.split("->").collect();
158            if arrow_parts.len() == 2 {
159                let params_str = arrow_parts[0].trim();
160                let return_str = arrow_parts[1].trim();
161
162                // Parse parameters
163                signature.param_lifetimes = parse_param_lifetimes(params_str);
164
165                // Parse return type
166                signature.return_lifetime = parse_single_lifetime(return_str);
167            }
168        } else {
169            // Just return type
170            signature.return_lifetime = parse_single_lifetime(main_part);
171        }
172
173        Some(signature)
174    } else if safety.is_some() {
175        // Even if no lifetime annotation, return signature if we have safety annotation
176        Some(FunctionSignature {
177            name: func_name,
178            return_lifetime: None,
179            param_lifetimes: Vec::new(),
180            lifetime_bounds: Vec::new(),
181            safety,
182        })
183    } else {
184        None
185    }
186}
187
188fn parse_param_lifetimes(params_str: &str) -> Vec<Option<LifetimeAnnotation>> {
189    let mut result = Vec::new();
190    
191    // Remove parentheses if present
192    let cleaned = params_str.trim_start_matches('(').trim_end_matches(')');
193    
194    // Split by comma
195    for param in cleaned.split(',') {
196        result.push(parse_single_lifetime(param.trim()));
197    }
198    
199    result
200}
201
202fn parse_single_lifetime(lifetime_str: &str) -> Option<LifetimeAnnotation> {
203    let trimmed = lifetime_str.trim();
204
205    if trimmed == "owned" {
206        Some(LifetimeAnnotation::Owned)
207    } else if trimmed.starts_with("&'") && trimmed.contains("mut") {
208        // &'a mut T
209        let lifetime_name = extract_lifetime_name(trimmed);
210        lifetime_name.map(|name| LifetimeAnnotation::MutRef(name))
211    } else if trimmed.starts_with("&'") {
212        // &'a T
213        let lifetime_name = extract_lifetime_name(trimmed);
214        lifetime_name.map(|name| LifetimeAnnotation::Ref(name))
215    } else if trimmed.starts_with('\'') {
216        // Just 'a
217        Some(LifetimeAnnotation::Lifetime(trimmed.to_string()))
218    } else if trimmed.contains("<&'") {
219        // CRITICAL FIX: Handle complex types like Option<&'a T> or Result<&'a T, E>
220        // Extract lifetime from inside template parameters
221        if trimmed.contains("mut") {
222            let lifetime_name = extract_lifetime_name(trimmed);
223            lifetime_name.map(|name| LifetimeAnnotation::MutRef(name))
224        } else {
225            let lifetime_name = extract_lifetime_name(trimmed);
226            lifetime_name.map(|name| LifetimeAnnotation::Ref(name))
227        }
228    } else {
229        None
230    }
231}
232
233fn extract_lifetime_name(s: &str) -> Option<String> {
234    let re = Regex::new(r"'([a-z][a-z0-9]*)").ok()?;
235    re.captures(s)
236        .and_then(|cap| cap.get(1))
237        .map(|m| m.as_str().to_string())
238}
239
240fn parse_lifetime_bounds(bounds_str: &str) -> Vec<LifetimeBound> {
241    let mut bounds = Vec::new();
242    
243    // Parse patterns like 'a: 'b
244    let bound_re = Regex::new(r"'([a-z][a-z0-9]*)\s*:\s*'([a-z][a-z0-9]*)").unwrap();
245    
246    for cap in bound_re.captures_iter(bounds_str) {
247        if let (Some(longer), Some(shorter)) = (cap.get(1), cap.get(2)) {
248            bounds.push(LifetimeBound {
249                longer: longer.as_str().to_string(),
250                shorter: shorter.as_str().to_string(),
251            });
252        }
253    }
254    
255    bounds
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    
262    #[test]
263    fn test_parse_simple_lifetime() {
264        let comment = "// @lifetime: &'a";
265        let sig = parse_lifetime_annotations(comment, "test".to_string()).unwrap();
266        
267        assert_eq!(sig.name, "test");
268        assert_eq!(sig.return_lifetime, Some(LifetimeAnnotation::Ref("a".to_string())));
269        assert!(sig.param_lifetimes.is_empty());
270    }
271    
272    #[test]
273    fn test_parse_owned() {
274        let comment = "// @lifetime: owned";
275        let sig = parse_lifetime_annotations(comment, "test".to_string()).unwrap();
276        
277        assert_eq!(sig.return_lifetime, Some(LifetimeAnnotation::Owned));
278    }
279    
280    #[test]
281    fn test_parse_with_params() {
282        let comment = "// @lifetime: (&'a, &'b) -> &'a where 'a: 'b";
283        let sig = parse_lifetime_annotations(comment, "test".to_string()).unwrap();
284        
285        assert_eq!(sig.param_lifetimes.len(), 2);
286        assert_eq!(sig.param_lifetimes[0], Some(LifetimeAnnotation::Ref("a".to_string())));
287        assert_eq!(sig.param_lifetimes[1], Some(LifetimeAnnotation::Ref("b".to_string())));
288        assert_eq!(sig.return_lifetime, Some(LifetimeAnnotation::Ref("a".to_string())));
289        assert_eq!(sig.lifetime_bounds.len(), 1);
290        assert_eq!(sig.lifetime_bounds[0].longer, "a");
291        assert_eq!(sig.lifetime_bounds[0].shorter, "b");
292    }
293    
294    #[test]
295    fn test_parse_mut_ref() {
296        let comment = "// @lifetime: &'a mut";
297        let sig = parse_lifetime_annotations(comment, "test".to_string()).unwrap();
298        
299        assert_eq!(sig.return_lifetime, Some(LifetimeAnnotation::MutRef("a".to_string())));
300    }
301}