#[cfg(feature = "decision-tracing")]
use crate::decision_trace::DecisionCategory;
use crate::hir::{AssignTarget, HirExpr, HirFunction, HirStmt, Type as PythonType};
use crate::trace_decision;
use crate::type_mapper::{RustType, TypeMapper};
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub struct BorrowingContext {
param_usage: HashMap<String, ParameterUsagePattern>,
moved_vars: HashSet<String>,
mut_borrowed_vars: HashSet<String>,
immut_borrowed_vars: HashSet<String>,
context_stack: Vec<AnalysisContext>,
return_type: Option<PythonType>,
}
#[derive(Debug, Clone, Default)]
pub struct ParameterUsagePattern {
pub is_read: bool,
pub is_mutated: bool,
pub is_moved: bool,
pub escapes_through_return: bool,
pub is_stored: bool,
pub used_in_closure: bool,
pub used_in_loop: bool,
pub field_accesses: HashSet<String>,
pub method_calls: HashSet<String>,
pub usage_sites: Vec<UsageSite>,
}
#[derive(Debug, Clone)]
pub struct UsageSite {
pub usage_type: UsageType,
pub in_loop: bool,
pub in_conditional: bool,
pub borrow_depth: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UsageType {
Read,
Write,
MethodCall(String),
FunctionArg { takes_ownership: bool },
Return,
Store,
Closure { captures_by_value: bool },
FieldAccess(String),
IndexAccess,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum AnalysisContext {
Loop,
Conditional,
Closure { captures: HashSet<String> },
Function,
}
#[derive(Debug, Clone)]
pub struct BorrowingAnalysisResult {
pub param_strategies: IndexMap<String, BorrowingStrategy>,
pub insights: Vec<BorrowingInsight>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BorrowingStrategy {
TakeOwnership,
BorrowImmutable { lifetime: Option<String> },
BorrowMutable { lifetime: Option<String> },
UseCow { lifetime: String },
UseSharedOwnership { is_thread_safe: bool },
}
#[derive(Debug, Clone)]
pub enum BorrowingInsight {
UnnecessaryMove(String),
LifetimeOptimization { param: String, suggestion: String },
SuggestCopyDerive(String),
PotentialBorrowConflict {
param: String,
locations: Vec<String>,
},
}
impl BorrowingContext {
pub fn new(return_type: Option<PythonType>) -> Self {
Self {
param_usage: HashMap::new(),
moved_vars: HashSet::new(),
mut_borrowed_vars: HashSet::new(),
immut_borrowed_vars: HashSet::new(),
context_stack: vec![AnalysisContext::Function],
return_type,
}
}
pub fn analyze_function(
&mut self,
func: &HirFunction,
type_mapper: &TypeMapper,
) -> BorrowingAnalysisResult {
for param in &func.params {
self.param_usage
.insert(param.name.clone(), ParameterUsagePattern::default());
}
for stmt in &func.body {
self.analyze_statement(stmt);
}
self.determine_strategies(func, type_mapper)
}
fn analyze_statement(&mut self, stmt: &HirStmt) {
match stmt {
HirStmt::Assign { target, value, .. } => {
let in_loop = self.is_in_loop();
let in_conditional = self.is_in_conditional();
self.check_target_mutation(target, in_loop, in_conditional);
self.analyze_expression(value, 0);
}
HirStmt::Return(expr) => {
if let Some(e) = expr {
self.analyze_expression_for_return(e);
}
}
HirStmt::If {
condition,
then_body,
else_body,
} => {
self.analyze_expression(condition, 0);
self.context_stack.push(AnalysisContext::Conditional);
for stmt in then_body {
self.analyze_statement(stmt);
}
if let Some(else_stmts) = else_body {
for stmt in else_stmts {
self.analyze_statement(stmt);
}
}
self.context_stack.pop();
}
HirStmt::While { condition, body } => {
self.context_stack.push(AnalysisContext::Loop);
self.analyze_expression(condition, 0);
for stmt in body {
self.analyze_statement(stmt);
}
self.context_stack.pop();
}
HirStmt::For {
target: _,
iter,
body,
} => {
self.context_stack.push(AnalysisContext::Loop);
self.analyze_expression(iter, 0);
for stmt in body {
self.analyze_statement(stmt);
}
self.context_stack.pop();
}
HirStmt::Expr(expr) => {
self.analyze_expression(expr, 0);
}
HirStmt::Raise { exception, cause } => {
if let Some(exc) = exception {
self.analyze_expression(exc, 0);
}
if let Some(c) = cause {
self.analyze_expression(c, 0);
}
}
HirStmt::Break { .. } | HirStmt::Continue { .. } | HirStmt::Pass => {
}
HirStmt::Block(stmts) => {
for s in stmts {
self.analyze_statement(s);
}
}
HirStmt::Assert { test, msg } => {
self.analyze_expression(test, 0);
if let Some(message) = msg {
self.analyze_expression(message, 0);
}
}
HirStmt::With {
context,
target: _,
body,
..
} => {
self.analyze_expression(context, 0);
for stmt in body {
self.analyze_statement(stmt);
}
}
HirStmt::Try {
body,
handlers,
orelse,
finalbody,
} => {
for stmt in body {
self.analyze_statement(stmt);
}
for handler in handlers {
for stmt in &handler.body {
self.analyze_statement(stmt);
}
}
if let Some(else_stmts) = orelse {
for stmt in else_stmts {
self.analyze_statement(stmt);
}
}
if let Some(finally_stmts) = finalbody {
for stmt in finally_stmts {
self.analyze_statement(stmt);
}
}
}
HirStmt::FunctionDef { body, .. } => {
for stmt in body {
self.analyze_statement(stmt);
}
}
}
}
fn check_target_mutation(
&mut self,
target: &AssignTarget,
in_loop: bool,
in_conditional: bool,
) {
match target {
AssignTarget::Symbol(symbol) => {
if let Some(usage) = self.param_usage.get_mut(symbol) {
usage.is_mutated = true;
usage.usage_sites.push(UsageSite {
usage_type: UsageType::Write,
in_loop,
in_conditional,
borrow_depth: 0,
});
}
}
AssignTarget::Index { base, .. } => {
if let HirExpr::Var(var_name) = base.as_ref() {
if let Some(usage) = self.param_usage.get_mut(var_name) {
usage.is_mutated = true;
usage.usage_sites.push(UsageSite {
usage_type: UsageType::Write,
in_loop,
in_conditional,
borrow_depth: 0,
});
}
}
}
AssignTarget::Attribute { value, .. } => {
if let HirExpr::Var(var_name) = value.as_ref() {
if let Some(usage) = self.param_usage.get_mut(var_name) {
usage.is_mutated = true;
usage.usage_sites.push(UsageSite {
usage_type: UsageType::Write,
in_loop,
in_conditional,
borrow_depth: 0,
});
}
}
}
AssignTarget::Tuple(targets) => {
for t in targets {
self.check_target_mutation(t, in_loop, in_conditional);
}
}
}
}
fn analyze_expression(&mut self, expr: &HirExpr, borrow_depth: usize) {
match expr {
HirExpr::Var(name) => {
let in_loop = self.is_in_loop();
let in_conditional = self.is_in_conditional();
if let Some(usage) = self.param_usage.get_mut(name) {
usage.is_read = true;
usage.usage_sites.push(UsageSite {
usage_type: UsageType::Read,
in_loop,
in_conditional,
borrow_depth,
});
if in_loop {
usage.used_in_loop = true;
}
}
}
HirExpr::Attribute { value, attr } => {
if let HirExpr::Var(name) = &**value {
let in_loop = self.is_in_loop();
let in_conditional = self.is_in_conditional();
if let Some(usage) = self.param_usage.get_mut(name) {
usage.field_accesses.insert(attr.clone());
usage.usage_sites.push(UsageSite {
usage_type: UsageType::FieldAccess(attr.clone()),
in_loop,
in_conditional,
borrow_depth: borrow_depth + 1,
});
}
}
self.analyze_expression(value, borrow_depth + 1);
}
HirExpr::Call { func, args, .. } => {
let in_loop = self.is_in_loop();
let in_conditional = self.is_in_conditional();
for (i, arg) in args.iter().enumerate() {
if let HirExpr::Var(name) = arg {
let takes_ownership = self.function_takes_ownership(func, i);
if let Some(usage) = self.param_usage.get_mut(name) {
if takes_ownership {
usage.is_moved = true;
self.moved_vars.insert(name.clone());
}
usage.usage_sites.push(UsageSite {
usage_type: UsageType::FunctionArg { takes_ownership },
in_loop,
in_conditional,
borrow_depth,
});
}
}
self.analyze_expression(arg, borrow_depth);
}
}
HirExpr::Index { base, index } => {
if let HirExpr::Var(name) = &**base {
let in_loop = self.is_in_loop();
let in_conditional = self.is_in_conditional();
if let Some(usage) = self.param_usage.get_mut(name) {
usage.usage_sites.push(UsageSite {
usage_type: UsageType::IndexAccess,
in_loop,
in_conditional,
borrow_depth: borrow_depth + 1,
});
}
}
self.analyze_expression(base, borrow_depth + 1);
self.analyze_expression(index, borrow_depth);
}
HirExpr::Binary { left, right, .. } => {
self.analyze_expression(left, borrow_depth);
self.analyze_expression(right, borrow_depth);
}
HirExpr::Unary { operand, .. } => {
self.analyze_expression(operand, borrow_depth);
}
HirExpr::List(elements) | HirExpr::Tuple(elements) => {
for elem in elements {
self.analyze_expression(elem, borrow_depth);
}
}
HirExpr::Dict(pairs) => {
for (k, v) in pairs {
self.analyze_expression(k, borrow_depth);
self.analyze_expression(v, borrow_depth);
}
}
HirExpr::Borrow { expr, mutable } => {
if let HirExpr::Var(name) = &**expr {
if *mutable {
self.mut_borrowed_vars.insert(name.clone());
} else {
self.immut_borrowed_vars.insert(name.clone());
}
}
self.analyze_expression(expr, borrow_depth + 1);
}
HirExpr::MethodCall { object, args, .. } => {
self.analyze_expression(object, borrow_depth);
for arg in args {
self.analyze_expression(arg, borrow_depth);
}
}
HirExpr::Slice {
base,
start,
stop,
step,
} => {
self.analyze_expression(base, borrow_depth);
if let Some(s) = start {
self.analyze_expression(s, borrow_depth);
}
if let Some(s) = stop {
self.analyze_expression(s, borrow_depth);
}
if let Some(s) = step {
self.analyze_expression(s, borrow_depth);
}
}
HirExpr::Literal(_) => {}
HirExpr::ListComp {
element,
generators,
} => {
self.context_stack.push(AnalysisContext::Loop);
for gen in generators {
self.analyze_expression(&gen.iter, borrow_depth);
for cond in &gen.conditions {
self.analyze_expression(cond, borrow_depth);
}
}
self.analyze_expression(element, borrow_depth);
self.context_stack.pop();
}
HirExpr::FString { .. } => {
}
HirExpr::Lambda { params: _, body } => {
self.analyze_expression(body, borrow_depth);
}
HirExpr::Set(elements) | HirExpr::FrozenSet(elements) => {
for elem in elements {
self.analyze_expression(elem, borrow_depth);
}
}
HirExpr::SetComp {
element,
generators,
} => {
self.context_stack.push(AnalysisContext::Loop);
for gen in generators {
self.analyze_expression(&gen.iter, borrow_depth);
for cond in &gen.conditions {
self.analyze_expression(cond, borrow_depth);
}
}
self.analyze_expression(element, borrow_depth);
self.context_stack.pop();
}
HirExpr::DictComp {
key,
value,
generators,
} => {
self.context_stack.push(AnalysisContext::Loop);
for gen in generators {
self.analyze_expression(&gen.iter, borrow_depth);
for cond in &gen.conditions {
self.analyze_expression(cond, borrow_depth);
}
}
self.analyze_expression(key, borrow_depth);
self.analyze_expression(value, borrow_depth);
self.context_stack.pop();
}
HirExpr::Await { value } => {
self.analyze_expression(value, borrow_depth);
}
HirExpr::Yield { value } => {
if let Some(v) = value {
self.analyze_expression(v, borrow_depth);
}
}
HirExpr::IfExpr { test, body, orelse } => {
self.analyze_expression(test, borrow_depth);
self.analyze_expression(body, borrow_depth);
self.analyze_expression(orelse, borrow_depth);
}
HirExpr::SortByKey {
iterable, key_body, ..
} => {
self.analyze_expression(iterable, borrow_depth);
self.analyze_expression(key_body, borrow_depth);
}
HirExpr::GeneratorExp {
element,
generators,
} => {
self.analyze_expression(element, borrow_depth);
for gen in generators {
self.analyze_expression(&gen.iter, borrow_depth);
for cond in &gen.conditions {
self.analyze_expression(cond, borrow_depth);
}
}
}
HirExpr::NamedExpr { value, .. } => {
self.analyze_expression(value, borrow_depth);
}
HirExpr::DynamicCall { callee, args, .. } => {
self.analyze_expression(callee, borrow_depth);
for arg in args {
self.analyze_expression(arg, borrow_depth);
}
}
}
}
fn analyze_expression_for_return(&mut self, expr: &HirExpr) {
match expr {
HirExpr::Var(name) => {
if let Some(usage) = self.param_usage.get_mut(name) {
usage.escapes_through_return = true;
usage.usage_sites.push(UsageSite {
usage_type: UsageType::Return,
in_loop: false,
in_conditional: false,
borrow_depth: 0,
});
}
}
HirExpr::Binary { left, right, .. } => {
self.analyze_expression(left, 0);
self.analyze_expression(right, 0);
}
_ => self.analyze_expression(expr, 0),
}
}
fn function_takes_ownership(&self, func_name: &str, _arg_index: usize) -> bool {
let borrowing_functions = [
"len",
"str",
"repr",
"format",
"print",
"isinstance",
"hasattr",
"getattr",
"contains",
"startswith",
"endswith",
"find",
"index",
"count",
"int",
"float",
"bool",
"sum",
"min",
"max",
"any",
"all",
"sorted",
"reversed",
"enumerate",
"zip",
"map",
"filter",
];
let ownership_functions = ["append", "extend", "insert", "remove", "pop", "sort"];
if borrowing_functions.contains(&func_name) {
false
} else if ownership_functions.contains(&func_name) {
true
} else {
false
}
}
fn is_in_loop(&self) -> bool {
self.context_stack
.iter()
.any(|ctx| matches!(ctx, AnalysisContext::Loop))
}
fn is_in_conditional(&self) -> bool {
self.context_stack
.iter()
.any(|ctx| matches!(ctx, AnalysisContext::Conditional))
}
fn determine_strategies(
&self,
func: &HirFunction,
type_mapper: &TypeMapper,
) -> BorrowingAnalysisResult {
let mut strategies = IndexMap::new();
let mut insights = Vec::new();
for param in &func.params {
let usage = self
.param_usage
.get(¶m.name)
.cloned()
.unwrap_or_default();
let rust_type = type_mapper.map_type(¶m.ty);
let strategy = self.determine_parameter_strategy(
¶m.name,
&usage,
&rust_type,
¶m.ty,
&mut insights,
);
strategies.insert(param.name.clone(), strategy);
}
BorrowingAnalysisResult {
param_strategies: strategies,
insights,
}
}
fn determine_parameter_strategy(
&self,
param_name: &str,
usage: &ParameterUsagePattern,
rust_type: &RustType,
python_type: &PythonType,
insights: &mut Vec<BorrowingInsight>,
) -> BorrowingStrategy {
trace_decision!(
category = DecisionCategory::BorrowStrategy,
name = "param_strategy",
chosen = param_name,
alternatives = ["owned", "borrowed", "mutable_ref", "copy", "arc"],
confidence = 0.85
);
if self.is_copy_type(rust_type) {
insights.push(BorrowingInsight::SuggestCopyDerive(param_name.to_string()));
}
if usage.is_moved {
if !usage.escapes_through_return && !usage.is_stored {
insights.push(BorrowingInsight::UnnecessaryMove(param_name.to_string()));
}
return BorrowingStrategy::TakeOwnership;
}
if usage.escapes_through_return && !matches!(python_type, PythonType::String) {
if let Some(ref ret_type) = self.return_type {
if python_type == ret_type {
return BorrowingStrategy::TakeOwnership;
}
}
}
if usage.is_stored {
return BorrowingStrategy::UseSharedOwnership {
is_thread_safe: false,
};
}
if usage.used_in_closure {
return BorrowingStrategy::TakeOwnership;
}
if self.is_copy_type(rust_type) {
return BorrowingStrategy::TakeOwnership; }
if matches!(python_type, PythonType::String) {
return self.determine_string_strategy(param_name, usage);
}
if usage.is_mutated {
BorrowingStrategy::BorrowMutable { lifetime: None }
} else if usage.is_read {
BorrowingStrategy::BorrowImmutable { lifetime: None }
} else {
BorrowingStrategy::TakeOwnership
}
}
fn determine_string_strategy(
&self,
_param_name: &str,
usage: &ParameterUsagePattern,
) -> BorrowingStrategy {
trace_decision!(
category = DecisionCategory::BorrowStrategy,
name = "string_strategy",
chosen = "string_analysis",
alternatives = ["String", "&str", "Cow", "AsRef", "Into"],
confidence = 0.82
);
if usage.is_moved {
return BorrowingStrategy::TakeOwnership;
}
if usage.is_mutated {
return BorrowingStrategy::TakeOwnership;
}
if usage.escapes_through_return {
return BorrowingStrategy::TakeOwnership;
}
if usage.is_read && !usage.is_moved && !usage.is_mutated {
return BorrowingStrategy::BorrowImmutable { lifetime: None };
}
BorrowingStrategy::TakeOwnership
}
#[allow(clippy::only_used_in_recursion)]
fn is_copy_type(&self, rust_type: &RustType) -> bool {
match rust_type {
RustType::Primitive(_) => true,
RustType::Unit => true,
RustType::Tuple(types) => types.iter().all(|t| self.is_copy_type(t)),
_ => false,
}
}
pub fn analyze_with_escape_analysis(
&mut self,
func: &crate::hir::HirFunction,
type_mapper: &TypeMapper,
) -> EnhancedBorrowingResult {
use crate::escape_analysis::{analyze_ownership, OwnershipFix};
let basic_result = self.analyze_function(func, type_mapper);
let ownership_result = analyze_ownership(func);
let mut ownership_fixes = Vec::new();
for error in &ownership_result.use_after_move_errors {
ownership_fixes.push(OwnershipFixSite {
variable: error.var.clone(),
site_index: error.move_site.stmt_index,
fix: match &error.fix {
OwnershipFix::Borrow => OwnershipFixType::UseBorrow,
OwnershipFix::MutableBorrow => OwnershipFixType::UseMutableBorrow,
OwnershipFix::Clone => OwnershipFixType::InsertClone,
OwnershipFix::CloneAtAssignment { var } => {
OwnershipFixType::CloneAtAssignment(var.clone())
}
OwnershipFix::Reject { reason } => {
OwnershipFixType::RejectPattern(reason.clone())
}
},
});
}
for pattern in &ownership_result.aliasing_patterns {
if pattern.source_used_after && pattern.alias_used_after {
ownership_fixes.push(OwnershipFixSite {
variable: pattern.alias.clone(),
site_index: 0, fix: OwnershipFixType::CloneAtAssignment(pattern.source.clone()),
});
}
}
EnhancedBorrowingResult {
basic_result,
use_after_move_count: ownership_result.use_after_move_errors.len(),
aliasing_pattern_count: ownership_result.aliasing_patterns.len(),
ownership_fixes,
}
}
}
#[derive(Debug, Clone)]
pub struct EnhancedBorrowingResult {
pub basic_result: BorrowingAnalysisResult,
pub use_after_move_count: usize,
pub aliasing_pattern_count: usize,
pub ownership_fixes: Vec<OwnershipFixSite>,
}
#[derive(Debug, Clone)]
pub struct OwnershipFixSite {
pub variable: String,
pub site_index: usize,
pub fix: OwnershipFixType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OwnershipFixType {
UseBorrow,
UseMutableBorrow,
InsertClone,
CloneAtAssignment(String),
RejectPattern(String),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::{FunctionProperties, HirParam, Literal};
use depyler_annotations::TranspilationAnnotations;
use smallvec::smallvec;
#[test]
fn test_basic_borrowing_analysis() {
let mut ctx = BorrowingContext::new(Some(PythonType::Int));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "add_one".to_string(),
params: smallvec![HirParam::new("x".to_string(), PythonType::Int)],
ret_type: PythonType::Int,
body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: crate::hir::BinOp::Add,
left: Box::new(HirExpr::Var("x".to_string())),
right: Box::new(HirExpr::Literal(Literal::Int(1))),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
let x_strategy = result.param_strategies.get("x").unwrap();
assert_eq!(*x_strategy, BorrowingStrategy::TakeOwnership);
}
#[test]
fn test_string_borrowing() {
let mut ctx = BorrowingContext::new(Some(PythonType::Int));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "string_len".to_string(),
params: smallvec![HirParam::new("s".to_string(), PythonType::String)],
ret_type: PythonType::Int,
body: vec![HirStmt::Return(Some(HirExpr::Call {
func: "len".to_string(),
args: vec![HirExpr::Var("s".to_string())],
kwargs: vec![],
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
let s_strategy = result.param_strategies.get("s").unwrap();
assert!(matches!(
s_strategy,
BorrowingStrategy::BorrowImmutable { .. }
));
}
#[test]
fn test_mutation_detection() {
let mut ctx = BorrowingContext::new(None);
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "mutate_list".to_string(),
params: smallvec![HirParam::new(
"lst".to_string(),
PythonType::List(Box::new(PythonType::Int))
)],
ret_type: PythonType::None,
body: vec![HirStmt::Expr(HirExpr::Call {
func: "append".to_string(),
args: vec![
HirExpr::Var("lst".to_string()),
HirExpr::Literal(Literal::Int(42)),
],
kwargs: vec![],
})],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
let lst_strategy = result.param_strategies.get("lst").unwrap();
assert_eq!(*lst_strategy, BorrowingStrategy::TakeOwnership);
}
#[test]
fn test_borrowing_context_new_with_none() {
let ctx = BorrowingContext::new(None);
assert!(ctx.param_usage.is_empty());
assert!(ctx.moved_vars.is_empty());
assert!(ctx.mut_borrowed_vars.is_empty());
assert!(ctx.immut_borrowed_vars.is_empty());
}
#[test]
fn test_borrowing_context_new_with_return_type() {
let ctx = BorrowingContext::new(Some(PythonType::String));
assert!(ctx.return_type.is_some());
}
#[test]
fn test_parameter_usage_pattern_default() {
let pattern = ParameterUsagePattern::default();
assert!(!pattern.is_read);
assert!(!pattern.is_mutated);
assert!(!pattern.is_moved);
assert!(!pattern.escapes_through_return);
assert!(!pattern.is_stored);
assert!(!pattern.used_in_closure);
assert!(!pattern.used_in_loop);
assert!(pattern.field_accesses.is_empty());
assert!(pattern.method_calls.is_empty());
assert!(pattern.usage_sites.is_empty());
}
#[test]
fn test_parameter_usage_pattern_clone() {
let pattern = ParameterUsagePattern {
is_read: true,
is_mutated: true,
..Default::default()
};
let cloned = pattern.clone();
assert_eq!(pattern.is_read, cloned.is_read);
assert_eq!(pattern.is_mutated, cloned.is_mutated);
}
#[test]
fn test_usage_site_creation() {
let site = UsageSite {
usage_type: UsageType::Read,
in_loop: true,
in_conditional: false,
borrow_depth: 1,
};
assert!(site.in_loop);
assert!(!site.in_conditional);
assert_eq!(site.borrow_depth, 1);
}
#[test]
fn test_usage_site_clone() {
let site = UsageSite {
usage_type: UsageType::Write,
in_loop: false,
in_conditional: true,
borrow_depth: 2,
};
let cloned = site.clone();
assert_eq!(site.in_loop, cloned.in_loop);
assert_eq!(site.borrow_depth, cloned.borrow_depth);
}
#[test]
fn test_usage_type_read() {
let usage = UsageType::Read;
assert_eq!(usage, UsageType::Read);
}
#[test]
fn test_usage_type_write() {
let usage = UsageType::Write;
assert_eq!(usage, UsageType::Write);
}
#[test]
fn test_usage_type_method_call() {
let usage = UsageType::MethodCall("append".to_string());
assert!(matches!(usage, UsageType::MethodCall(_)));
}
#[test]
fn test_usage_type_function_arg_owned() {
let usage = UsageType::FunctionArg {
takes_ownership: true,
};
assert!(matches!(
usage,
UsageType::FunctionArg {
takes_ownership: true
}
));
}
#[test]
fn test_usage_type_function_arg_borrowed() {
let usage = UsageType::FunctionArg {
takes_ownership: false,
};
assert!(matches!(
usage,
UsageType::FunctionArg {
takes_ownership: false
}
));
}
#[test]
fn test_usage_type_return() {
let usage = UsageType::Return;
assert_eq!(usage, UsageType::Return);
}
#[test]
fn test_usage_type_store() {
let usage = UsageType::Store;
assert_eq!(usage, UsageType::Store);
}
#[test]
fn test_usage_type_closure() {
let usage = UsageType::Closure {
captures_by_value: true,
};
assert!(matches!(
usage,
UsageType::Closure {
captures_by_value: true
}
));
}
#[test]
fn test_usage_type_field_access() {
let usage = UsageType::FieldAccess("name".to_string());
assert!(matches!(usage, UsageType::FieldAccess(_)));
}
#[test]
fn test_usage_type_index_access() {
let usage = UsageType::IndexAccess;
assert_eq!(usage, UsageType::IndexAccess);
}
#[test]
fn test_usage_type_clone() {
let usage = UsageType::MethodCall("push".to_string());
let cloned = usage.clone();
assert_eq!(usage, cloned);
}
#[test]
fn test_borrowing_strategy_take_ownership() {
let strategy = BorrowingStrategy::TakeOwnership;
assert_eq!(strategy, BorrowingStrategy::TakeOwnership);
}
#[test]
fn test_borrowing_strategy_borrow_immutable() {
let strategy = BorrowingStrategy::BorrowImmutable { lifetime: None };
assert!(matches!(
strategy,
BorrowingStrategy::BorrowImmutable { .. }
));
}
#[test]
fn test_borrowing_strategy_borrow_immutable_with_lifetime() {
let strategy = BorrowingStrategy::BorrowImmutable {
lifetime: Some("'a".to_string()),
};
if let BorrowingStrategy::BorrowImmutable { lifetime } = &strategy {
assert_eq!(lifetime, &Some("'a".to_string()));
}
}
#[test]
fn test_borrowing_strategy_borrow_mutable() {
let strategy = BorrowingStrategy::BorrowMutable { lifetime: None };
assert!(matches!(strategy, BorrowingStrategy::BorrowMutable { .. }));
}
#[test]
fn test_borrowing_strategy_use_cow() {
let strategy = BorrowingStrategy::UseCow {
lifetime: "'a".to_string(),
};
if let BorrowingStrategy::UseCow { lifetime } = &strategy {
assert_eq!(lifetime, "'a");
}
}
#[test]
fn test_borrowing_strategy_use_shared_ownership_arc() {
let strategy = BorrowingStrategy::UseSharedOwnership {
is_thread_safe: true,
};
if let BorrowingStrategy::UseSharedOwnership { is_thread_safe } = strategy {
assert!(is_thread_safe);
}
}
#[test]
fn test_borrowing_strategy_use_shared_ownership_rc() {
let strategy = BorrowingStrategy::UseSharedOwnership {
is_thread_safe: false,
};
if let BorrowingStrategy::UseSharedOwnership { is_thread_safe } = strategy {
assert!(!is_thread_safe);
}
}
#[test]
fn test_borrowing_strategy_clone() {
let strategy = BorrowingStrategy::TakeOwnership;
let cloned = strategy.clone();
assert_eq!(strategy, cloned);
}
#[test]
fn test_borrowing_insight_unnecessary_move() {
let insight = BorrowingInsight::UnnecessaryMove("x".to_string());
assert!(matches!(insight, BorrowingInsight::UnnecessaryMove(_)));
}
#[test]
fn test_borrowing_insight_lifetime_optimization() {
let insight = BorrowingInsight::LifetimeOptimization {
param: "s".to_string(),
suggestion: "Use 'a".to_string(),
};
assert!(matches!(
insight,
BorrowingInsight::LifetimeOptimization { .. }
));
}
#[test]
fn test_borrowing_insight_suggest_copy() {
let insight = BorrowingInsight::SuggestCopyDerive("Point".to_string());
assert!(matches!(insight, BorrowingInsight::SuggestCopyDerive(_)));
}
#[test]
fn test_borrowing_insight_borrow_conflict() {
let insight = BorrowingInsight::PotentialBorrowConflict {
param: "data".to_string(),
locations: vec!["line 10".to_string(), "line 20".to_string()],
};
assert!(matches!(
insight,
BorrowingInsight::PotentialBorrowConflict { .. }
));
}
#[test]
fn test_borrowing_insight_clone() {
let insight = BorrowingInsight::UnnecessaryMove("y".to_string());
let cloned = insight.clone();
assert!(matches!(cloned, BorrowingInsight::UnnecessaryMove(_)));
}
#[test]
fn test_borrowing_analysis_result_creation() {
let result = BorrowingAnalysisResult {
param_strategies: IndexMap::new(),
insights: Vec::new(),
};
assert!(result.param_strategies.is_empty());
assert!(result.insights.is_empty());
}
#[test]
fn test_borrowing_analysis_result_with_strategies() {
let mut strategies = IndexMap::new();
strategies.insert("x".to_string(), BorrowingStrategy::TakeOwnership);
let result = BorrowingAnalysisResult {
param_strategies: strategies,
insights: Vec::new(),
};
assert_eq!(result.param_strategies.len(), 1);
assert!(result.param_strategies.contains_key("x"));
}
#[test]
fn test_borrowing_analysis_result_clone() {
let result = BorrowingAnalysisResult {
param_strategies: IndexMap::new(),
insights: vec![BorrowingInsight::SuggestCopyDerive("T".to_string())],
};
let cloned = result.clone();
assert_eq!(result.insights.len(), cloned.insights.len());
}
#[test]
fn test_analyze_function_with_float_param() {
let mut ctx = BorrowingContext::new(Some(PythonType::Float));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "double".to_string(),
params: smallvec![HirParam::new("x".to_string(), PythonType::Float)],
ret_type: PythonType::Float,
body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: crate::hir::BinOp::Mul,
left: Box::new(HirExpr::Var("x".to_string())),
right: Box::new(HirExpr::Literal(Literal::Float(2.0))),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
let x_strategy = result.param_strategies.get("x").unwrap();
assert_eq!(*x_strategy, BorrowingStrategy::TakeOwnership);
}
#[test]
fn test_analyze_function_with_bool_param() {
let mut ctx = BorrowingContext::new(Some(PythonType::Bool));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "negate".to_string(),
params: smallvec![HirParam::new("b".to_string(), PythonType::Bool)],
ret_type: PythonType::Bool,
body: vec![HirStmt::Return(Some(HirExpr::Unary {
op: crate::hir::UnaryOp::Not,
operand: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
let b_strategy = result.param_strategies.get("b").unwrap();
assert_eq!(*b_strategy, BorrowingStrategy::TakeOwnership);
}
#[test]
fn test_analyze_function_empty_body() {
let mut ctx = BorrowingContext::new(None);
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "noop".to_string(),
params: smallvec![HirParam::new("x".to_string(), PythonType::Int)],
ret_type: PythonType::None,
body: vec![],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
assert!(result.param_strategies.contains_key("x"));
}
#[test]
fn test_analyze_function_no_params() {
let mut ctx = BorrowingContext::new(Some(PythonType::Int));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "get_zero".to_string(),
params: smallvec![],
ret_type: PythonType::Int,
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(0))))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
assert!(result.param_strategies.is_empty());
}
#[test]
fn test_analyze_function_multiple_params() {
let mut ctx = BorrowingContext::new(Some(PythonType::Int));
let type_mapper = TypeMapper::new();
let func = HirFunction {
name: "add".to_string(),
params: smallvec![
HirParam::new("a".to_string(), PythonType::Int),
HirParam::new("b".to_string(), PythonType::Int)
],
ret_type: PythonType::Int,
body: vec![HirStmt::Return(Some(HirExpr::Binary {
op: crate::hir::BinOp::Add,
left: Box::new(HirExpr::Var("a".to_string())),
right: Box::new(HirExpr::Var("b".to_string())),
}))],
properties: FunctionProperties::default(),
annotations: TranspilationAnnotations::default(),
docstring: None,
};
let result = ctx.analyze_function(&func, &type_mapper);
assert_eq!(result.param_strategies.len(), 2);
assert!(result.param_strategies.contains_key("a"));
assert!(result.param_strategies.contains_key("b"));
}
#[test]
fn test_analysis_context_loop_debug() {
let ctx = AnalysisContext::Loop;
let debug = format!("{:?}", ctx);
assert!(debug.contains("Loop"));
}
#[test]
fn test_analysis_context_conditional_debug() {
let ctx = AnalysisContext::Conditional;
let debug = format!("{:?}", ctx);
assert!(debug.contains("Conditional"));
}
#[test]
fn test_analysis_context_closure_debug() {
let ctx = AnalysisContext::Closure {
captures: HashSet::from(["x".to_string(), "y".to_string()]),
};
let debug = format!("{:?}", ctx);
assert!(debug.contains("Closure"));
assert!(debug.contains("captures"));
}
#[test]
fn test_analysis_context_function_debug() {
let ctx = AnalysisContext::Function;
let debug = format!("{:?}", ctx);
assert!(debug.contains("Function"));
}
#[test]
fn test_analysis_context_clone() {
let ctx = AnalysisContext::Closure {
captures: HashSet::from(["var".to_string()]),
};
let cloned = ctx.clone();
if let AnalysisContext::Closure { captures } = cloned {
assert!(captures.contains("var"));
} else {
panic!("Expected Closure variant");
}
}
#[test]
fn test_borrowing_context_debug() {
let ctx = BorrowingContext::new(None);
let debug = format!("{:?}", ctx);
assert!(debug.contains("BorrowingContext"));
}
#[test]
fn test_borrowing_context_initial_state() {
let ctx = BorrowingContext::new(Some(PythonType::String));
assert!(ctx.param_usage.is_empty());
assert!(ctx.moved_vars.is_empty());
assert!(ctx.mut_borrowed_vars.is_empty());
assert!(ctx.immut_borrowed_vars.is_empty());
assert!(!ctx.context_stack.is_empty()); }
#[test]
fn test_parameter_usage_pattern_all_fields() {
let mut pattern = ParameterUsagePattern {
is_read: true,
is_mutated: true,
is_moved: false,
escapes_through_return: true,
is_stored: false,
used_in_closure: true,
used_in_loop: true,
..Default::default()
};
pattern.field_accesses.insert("name".to_string());
pattern.method_calls.insert("len".to_string());
assert!(pattern.is_read);
assert!(pattern.is_mutated);
assert!(!pattern.is_moved);
assert!(pattern.escapes_through_return);
assert!(!pattern.is_stored);
assert!(pattern.used_in_closure);
assert!(pattern.used_in_loop);
assert!(pattern.field_accesses.contains("name"));
assert!(pattern.method_calls.contains("len"));
}
#[test]
fn test_parameter_usage_pattern_debug() {
let pattern = ParameterUsagePattern::default();
let debug = format!("{:?}", pattern);
assert!(debug.contains("ParameterUsagePattern"));
assert!(debug.contains("is_read"));
}
#[test]
fn test_usage_site_all_fields() {
let site = UsageSite {
usage_type: UsageType::Write,
in_loop: true,
in_conditional: false,
borrow_depth: 2,
};
assert!(matches!(site.usage_type, UsageType::Write));
assert!(site.in_loop);
assert!(!site.in_conditional);
assert_eq!(site.borrow_depth, 2);
}
#[test]
fn test_usage_site_debug() {
let site = UsageSite {
usage_type: UsageType::Read,
in_loop: false,
in_conditional: true,
borrow_depth: 0,
};
let debug = format!("{:?}", site);
assert!(debug.contains("UsageSite"));
assert!(debug.contains("Read"));
}
#[test]
fn test_usage_type_read_eq() {
assert_eq!(UsageType::Read, UsageType::Read);
assert_ne!(UsageType::Read, UsageType::Write);
}
#[test]
fn test_usage_type_closure_variants() {
let by_value = UsageType::Closure {
captures_by_value: true,
};
let by_ref = UsageType::Closure {
captures_by_value: false,
};
assert_ne!(by_value, by_ref);
}
#[test]
fn test_borrowing_strategy_eq() {
assert_eq!(
BorrowingStrategy::TakeOwnership,
BorrowingStrategy::TakeOwnership
);
assert_ne!(
BorrowingStrategy::TakeOwnership,
BorrowingStrategy::BorrowMutable { lifetime: None }
);
}
#[test]
fn test_borrowing_strategy_debug_all() {
let strategies = [
BorrowingStrategy::TakeOwnership,
BorrowingStrategy::BorrowImmutable { lifetime: None },
BorrowingStrategy::BorrowImmutable {
lifetime: Some("a".to_string()),
},
BorrowingStrategy::BorrowMutable { lifetime: None },
BorrowingStrategy::UseCow {
lifetime: "b".to_string(),
},
BorrowingStrategy::UseSharedOwnership {
is_thread_safe: true,
},
];
for strategy in strategies {
let debug = format!("{:?}", strategy);
assert!(!debug.is_empty());
}
}
#[test]
fn test_borrowing_insight_debug_all() {
let insights = [
BorrowingInsight::UnnecessaryMove("x".to_string()),
BorrowingInsight::LifetimeOptimization {
param: "p".to_string(),
suggestion: "Consider borrow".to_string(),
},
BorrowingInsight::SuggestCopyDerive("y".to_string()),
BorrowingInsight::PotentialBorrowConflict {
param: "z".to_string(),
locations: vec!["line 1".to_string(), "line 2".to_string()],
},
];
for insight in insights {
let debug = format!("{:?}", insight);
assert!(!debug.is_empty());
}
}
#[test]
fn test_borrowing_analysis_result_debug() {
let result = BorrowingAnalysisResult {
param_strategies: IndexMap::new(),
insights: vec![],
};
let debug = format!("{:?}", result);
assert!(debug.contains("BorrowingAnalysisResult"));
}
#[test]
fn test_borrowing_analysis_result_with_insights() {
let result = BorrowingAnalysisResult {
param_strategies: IndexMap::new(),
insights: vec![
BorrowingInsight::SuggestCopyDerive("val".to_string()),
BorrowingInsight::LifetimeOptimization {
param: "p".to_string(),
suggestion: "Borrow instead".to_string(),
},
],
};
assert_eq!(result.insights.len(), 2);
}
}