use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct TypeInfo {
pub name: String,
pub is_copy: bool,
pub is_clone: bool,
pub is_drop: bool,
pub size: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct RustdocDatabase {
pub types: HashMap<String, TypeInfo>,
pub impls: HashMap<String, Vec<ImplInfo>>,
}
#[derive(Debug, Clone)]
pub struct ImplInfo {
pub type_name: String,
pub trait_name: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OwnershipOp {
Move {
target: String,
source: String,
line: usize,
},
CallMove {
arg_name: String,
func_name: String,
arg_index: usize,
line: usize,
},
Borrow {
target: String,
is_mut: bool,
line: usize,
},
}
pub struct RustdocExtractor {
_json_path: PathBuf,
}
impl RustdocExtractor {
pub fn new(json_path: PathBuf) -> Self {
Self {
_json_path: json_path,
}
}
pub fn extract(&self) -> crate::error::MemScopeResult<RustdocDatabase> {
let mut db = RustdocDatabase {
types: HashMap::new(),
impls: HashMap::new(),
};
db.types.insert(
"i32".to_string(),
TypeInfo {
name: "i32".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(4),
},
);
db.types.insert(
"i64".to_string(),
TypeInfo {
name: "i64".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(8),
},
);
db.types.insert(
"f32".to_string(),
TypeInfo {
name: "f32".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(4),
},
);
db.types.insert(
"f64".to_string(),
TypeInfo {
name: "f64".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(8),
},
);
db.types.insert(
"bool".to_string(),
TypeInfo {
name: "bool".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(1),
},
);
db.types.insert(
"usize".to_string(),
TypeInfo {
name: "usize".to_string(),
is_copy: true,
is_clone: true,
is_drop: false,
size: Some(8),
},
);
db.types.insert(
"String".to_string(),
TypeInfo {
name: "String".to_string(),
is_copy: false,
is_clone: true,
is_drop: true,
size: Some(24),
},
);
db.types.insert(
"Vec".to_string(),
TypeInfo {
name: "Vec".to_string(),
is_copy: false,
is_clone: true,
is_drop: true,
size: Some(24),
},
);
Ok(db)
}
}
pub struct AstAnalyzer {
source_code: String,
}
impl AstAnalyzer {
pub fn new(source_code: String) -> Self {
Self { source_code }
}
pub fn analyze(&self) -> Vec<OwnershipOp> {
let mut ops = Vec::new();
for (line_num, line) in self.source_code.lines().enumerate() {
self.detect_move_operations(line, line_num, &mut ops);
self.detect_borrow_operations(line, line_num, &mut ops);
self.detect_function_calls(line, line_num, &mut ops);
}
ops
}
fn detect_move_operations(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
if line.contains('=') && !line.contains('&') {
let parts: Vec<&str> = line.split('=').collect();
if parts.len() == 2 {
let target = parts[0].split_whitespace().next().unwrap_or("");
let source = parts[1].split_whitespace().next().unwrap_or("");
if !source.is_empty()
&& source.parse::<i32>().is_err()
&& source.parse::<f64>().is_err()
&& !source.starts_with('"')
{
ops.push(OwnershipOp::Move {
target: target.to_string(),
source: source.to_string(),
line: line_num,
});
}
}
}
}
fn detect_borrow_operations(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
if line.contains('&') {
let is_mut = line.contains("&mut");
let var = line
.split('&')
.nth(1)
.and_then(|s| s.split_whitespace().next())
.unwrap_or("");
if !var.is_empty() {
ops.push(OwnershipOp::Borrow {
target: var.to_string(),
is_mut,
line: line_num,
});
}
}
}
fn detect_function_calls(&self, line: &str, line_num: usize, ops: &mut Vec<OwnershipOp>) {
if line.contains('(') && line.contains(')') {
let func_name = line
.split('(')
.next()
.and_then(|s| s.split_whitespace().last())
.unwrap_or("");
let args = line
.split('(')
.nth(1)
.and_then(|s| s.split(')').next())
.unwrap_or("");
if self.is_ownership_taking_function(func_name) {
for (arg_idx, arg) in args.split(',').enumerate() {
let arg_name = arg.split_whitespace().next().unwrap_or("");
if !arg_name.is_empty() {
ops.push(OwnershipOp::CallMove {
arg_name: arg_name.to_string(),
func_name: func_name.to_string(),
arg_index: arg_idx,
line: line_num,
});
}
}
}
}
}
fn is_ownership_taking_function(&self, func_name: &str) -> bool {
matches!(
func_name,
"into_iter" | "into_vec" | "into_string" | "into_boxed_slice" | "collect" | "consume"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rustdoc_extractor() {
let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
let db = extractor.extract().unwrap();
assert!(db.types.contains_key("i32"));
assert!(db.types.contains_key("String"));
}
#[test]
fn test_type_info_copy() {
let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
let db = extractor.extract().unwrap();
let i32_info = db.types.get("i32").unwrap();
assert!(i32_info.is_copy);
}
#[test]
fn test_type_info_string_not_copy() {
let extractor = RustdocExtractor::new(PathBuf::from("test.json"));
let db = extractor.extract().unwrap();
let string_info = db.types.get("String").unwrap();
assert!(!string_info.is_copy);
assert!(string_info.is_clone);
}
#[test]
fn test_ast_analyzer_move() {
let source = "let y = x;";
let analyzer = AstAnalyzer::new(source.to_string());
let ops = analyzer.analyze();
assert!(!ops.is_empty());
assert!(matches!(ops[0], OwnershipOp::Move { .. }));
}
#[test]
fn test_ast_analyzer_borrow() {
let source = "let y = &x;";
let analyzer = AstAnalyzer::new(source.to_string());
let ops = analyzer.analyze();
assert!(!ops.is_empty());
assert!(matches!(ops[0], OwnershipOp::Borrow { .. }));
}
#[test]
fn test_ast_analyzer_mut_borrow() {
let source = "let y = &mut x;";
let analyzer = AstAnalyzer::new(source.to_string());
let ops = analyzer.analyze();
assert!(!ops.is_empty());
if let OwnershipOp::Borrow { is_mut, .. } = &ops[0] {
assert!(*is_mut);
}
}
#[test]
fn test_ast_analyzer_function_call() {
let source = "let iter = vec.into_iter();";
let analyzer = AstAnalyzer::new(source.to_string());
let ops = analyzer.analyze();
assert!(!ops.is_empty());
}
}