1use std::collections::HashMap;
17use std::path::PathBuf;
18
19#[derive(Debug, Clone)]
21pub struct TypeInfo {
22 pub name: String,
23 pub is_copy: bool,
24 pub is_clone: bool,
25 pub is_drop: bool,
26 pub size: Option<usize>,
27}
28
29#[derive(Debug, Clone)]
31pub struct RustdocDatabase {
32 pub types: HashMap<String, TypeInfo>,
33 pub impls: HashMap<String, Vec<ImplInfo>>,
34}
35
36#[derive(Debug, Clone)]
38pub struct ImplInfo {
39 pub type_name: String,
40 pub trait_name: String,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum OwnershipOp {
46 Move {
47 target: String,
48 source: String,
49 line: usize,
50 },
51 CallMove {
52 arg_name: String,
53 func_name: String,
54 arg_index: usize,
55 line: usize,
56 },
57 Borrow {
58 target: String,
59 is_mut: bool,
60 line: usize,
61 },
62}
63
64pub struct RustdocExtractor {
66 _json_path: PathBuf,
67}
68
69impl RustdocExtractor {
70 pub fn new(json_path: PathBuf) -> Self {
71 Self {
72 _json_path: json_path,
73 }
74 }
75
76 pub fn extract(&self) -> crate::error::MemScopeResult<RustdocDatabase> {
78 let mut db = RustdocDatabase {
81 types: HashMap::new(),
82 impls: HashMap::new(),
83 };
84
85 db.types.insert(
87 "i32".to_string(),
88 TypeInfo {
89 name: "i32".to_string(),
90 is_copy: true,
91 is_clone: true,
92 is_drop: false,
93 size: Some(4),
94 },
95 );
96 db.types.insert(
97 "i64".to_string(),
98 TypeInfo {
99 name: "i64".to_string(),
100 is_copy: true,
101 is_clone: true,
102 is_drop: false,
103 size: Some(8),
104 },
105 );
106 db.types.insert(
107 "f32".to_string(),
108 TypeInfo {
109 name: "f32".to_string(),
110 is_copy: true,
111 is_clone: true,
112 is_drop: false,
113 size: Some(4),
114 },
115 );
116 db.types.insert(
117 "f64".to_string(),
118 TypeInfo {
119 name: "f64".to_string(),
120 is_copy: true,
121 is_clone: true,
122 is_drop: false,
123 size: Some(8),
124 },
125 );
126 db.types.insert(
127 "bool".to_string(),
128 TypeInfo {
129 name: "bool".to_string(),
130 is_copy: true,
131 is_clone: true,
132 is_drop: false,
133 size: Some(1),
134 },
135 );
136 db.types.insert(
137 "usize".to_string(),
138 TypeInfo {
139 name: "usize".to_string(),
140 is_copy: true,
141 is_clone: true,
142 is_drop: false,
143 size: Some(8),
144 },
145 );
146 db.types.insert(
147 "String".to_string(),
148 TypeInfo {
149 name: "String".to_string(),
150 is_copy: false,
151 is_clone: true,
152 is_drop: true,
153 size: Some(24),
154 },
155 );
156 db.types.insert(
157 "Vec".to_string(),
158 TypeInfo {
159 name: "Vec".to_string(),
160 is_copy: false,
161 is_clone: true,
162 is_drop: true,
163 size: Some(24),
164 },
165 );
166
167 Ok(db)
168 }
169}
170
171pub struct AstAnalyzer {
173 source_code: String,
174}
175
176impl AstAnalyzer {
177 pub fn new(source_code: String) -> Self {
178 Self { source_code }
179 }
180
181 pub fn analyze(&self) -> Vec<OwnershipOp> {
183 let mut ops = Vec::new();
184
185 for (line_num, line) in self.source_code.lines().enumerate() {
188 self.detect_move_operations(line, line_num, &mut ops);
189 self.detect_borrow_operations(line, line_num, &mut ops);
190 self.detect_function_calls(line, line_num, &mut ops);
191 }
192
193 ops
194 }
195
196 fn detect_move_operations(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
197 if line.contains('=') && !line.contains('&') {
199 let parts: Vec<&str> = line.split('=').collect();
200 if parts.len() == 2 {
201 let target = parts[0].split_whitespace().next().unwrap_or("");
202 let source = parts[1].split_whitespace().next().unwrap_or("");
203
204 if !source.is_empty()
206 && source.parse::<i32>().is_err()
207 && source.parse::<f64>().is_err()
208 && !source.starts_with('"')
209 {
210 ops.push(OwnershipOp::Move {
211 target: target.to_string(),
212 source: source.to_string(),
213 line: line_num,
214 });
215 }
216 }
217 }
218 }
219
220 fn detect_borrow_operations(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
221 if line.contains('&') {
223 let is_mut = line.contains("&mut");
224 let var = line
226 .split('&')
227 .nth(1)
228 .and_then(|s| s.split_whitespace().next())
229 .unwrap_or("");
230
231 if !var.is_empty() {
232 ops.push(OwnershipOp::Borrow {
233 target: var.to_string(),
234 is_mut,
235 line: line_num,
236 });
237 }
238 }
239 }
240
241 fn detect_function_calls(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
242 if line.contains('(') && line.contains(')') {
244 let func_name = line
245 .split('(')
246 .next()
247 .and_then(|s| s.split_whitespace().last())
248 .unwrap_or("");
249
250 let args = line
251 .split('(')
252 .nth(1)
253 .and_then(|s| s.split(')').next())
254 .unwrap_or("");
255
256 if self.is_ownership_taking_function(func_name) {
258 for (arg_idx, arg) in args.split(',').enumerate() {
259 let arg_name = arg.split_whitespace().next().unwrap_or("");
260 if !arg_name.is_empty() {
261 ops.push(OwnershipOp::CallMove {
262 arg_name: arg_name.to_string(),
263 func_name: func_name.to_string(),
264 arg_index: arg_idx,
265 line: line_num,
266 });
267 }
268 }
269 }
270 }
271 }
272
273 fn is_ownership_taking_function(&self, func_name: &str) -> bool {
274 matches!(
277 func_name,
278 "into_iter" | "into_vec" | "into_string" | "into_boxed_slice" | "collect" | "consume"
279 )
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_rustdoc_extractor() {
289 let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
290 let db = extractor.extract().unwrap();
291
292 assert!(db.types.contains_key("i32"));
293 assert!(db.types.contains_key("String"));
294 }
295
296 #[test]
297 fn test_type_info_copy() {
298 let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
299 let db = extractor.extract().unwrap();
300
301 let i32_info = db.types.get("i32").unwrap();
302 assert!(i32_info.is_copy);
303 }
304
305 #[test]
306 fn test_type_info_string_not_copy() {
307 let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
308 let db = extractor.extract().unwrap();
309
310 let string_info = db.types.get("String").unwrap();
311 assert!(!string_info.is_copy);
312 assert!(string_info.is_clone);
313 }
314
315 #[test]
316 fn test_ast_analyzer_move() {
317 let source = "let y = x;";
318 let analyzer = AstAnalyzer::new(source.to_string());
319 let ops = analyzer.analyze();
320
321 assert!(!ops.is_empty());
322 assert!(matches!(ops[0], OwnershipOp::Move { .. }));
323 }
324
325 #[test]
326 fn test_ast_analyzer_borrow() {
327 let source = "let y = &x;";
328 let analyzer = AstAnalyzer::new(source.to_string());
329 let ops = analyzer.analyze();
330
331 assert!(!ops.is_empty());
332 assert!(matches!(ops[0], OwnershipOp::Borrow { .. }));
333 }
334
335 #[test]
336 fn test_ast_analyzer_mut_borrow() {
337 let source = "let y = &mut x;";
338 let analyzer = AstAnalyzer::new(source.to_string());
339 let ops = analyzer.analyze();
340
341 assert!(!ops.is_empty());
342 if let OwnershipOp::Borrow { is_mut, .. } = &ops[0] {
343 assert!(*is_mut);
344 }
345 }
346
347 #[test]
348 fn test_ast_analyzer_function_call() {
349 let source = "let iter = vec.into_iter();";
350 let analyzer = AstAnalyzer::new(source.to_string());
351 let ops = analyzer.analyze();
352
353 assert!(!ops.is_empty());
355 }
356}