use ryo_source::pure::{PureFields, PureFile, PureItem, PureType};
use ryo_symbol::SymbolId;
use super::detect::{Detect, DetectCategory, DetectLocation, DetectOperation, DetectOpportunity};
use crate::Mutation;
#[derive(Debug, Clone, Default)]
pub struct UseAtomicMutation {
pub target_struct: Option<String>,
}
impl UseAtomicMutation {
pub fn new() -> Self {
Self::default()
}
pub fn for_struct(mut self, name: impl Into<String>) -> Self {
self.target_struct = Some(name.into());
self
}
fn is_atomic_candidate(field_name: &str) -> Option<&'static str> {
let lower = field_name.to_lowercase();
if lower.contains("count")
|| lower.contains("counter")
|| lower.contains("num")
|| lower.contains("total")
|| lower.contains("size")
|| lower.contains("len")
{
return Some("AtomicUsize");
}
if lower.contains("flag")
|| lower.contains("enabled")
|| lower.contains("active")
|| lower.contains("ready")
|| lower.contains("done")
|| lower.starts_with("is_")
{
return Some("AtomicBool");
}
if lower.contains("id") || lower.contains("index") || lower.contains("seq") {
return Some("AtomicU64");
}
None
}
fn detect_opportunities(&self, file: &PureFile) -> Vec<AtomicOpportunity> {
let mut opportunities = Vec::new();
for item in &file.items {
if let PureItem::Struct(s) = item {
if let Some(ref target) = self.target_struct {
if &s.name != target {
continue;
}
}
if let PureFields::Named(fields) = &s.fields {
for field in fields {
let type_str = match &field.ty {
PureType::Path(p) => p.as_str(),
_ => continue,
};
if type_str.contains("Mutex<") {
if let Some(atomic_type) = Self::is_atomic_candidate(&field.name) {
opportunities.push(AtomicOpportunity {
struct_name: s.name.clone(),
field_name: field.name.clone(),
suggested_type: atomic_type.to_string(),
});
}
}
}
}
}
}
opportunities
}
}
#[derive(Debug)]
struct AtomicOpportunity {
struct_name: String,
field_name: String,
suggested_type: String,
}
impl Mutation for UseAtomicMutation {
fn describe(&self) -> String {
"Replace Mutex<T> with atomic types for simple counter/flag fields".to_string()
}
fn mutation_type(&self) -> &'static str {
"UseAtomic"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
impl Detect for UseAtomicMutation {
fn detect(&self, file: &PureFile) -> Vec<DetectOpportunity> {
self.detect_opportunities(file)
.into_iter()
.map(|o| {
DetectOpportunity::new(
DetectLocation::struct_item(&o.struct_name),
format!(
"Consider using {} for field '{}' instead of Mutex",
o.suggested_type, o.field_name
),
)
.with_operations(vec![DetectOperation::Refactor])
.with_confidence(0.7)
.with_context(format!(
"field:{},suggested:{}",
o.field_name, o.suggested_type
))
})
.collect()
}
fn category(&self) -> DetectCategory {
DetectCategory::Performance
}
fn detect_name(&self) -> &'static str {
"UseAtomic"
}
fn detect_description(&self) -> &str {
"Replace Mutex<T> with atomic types for simple counter/flag fields"
}
}
#[derive(Debug, Clone, Default)]
pub struct UseRwLockMutation {
pub target_struct: Option<String>,
}
impl UseRwLockMutation {
pub fn new() -> Self {
Self::default()
}
pub fn for_struct(mut self, name: impl Into<String>) -> Self {
self.target_struct = Some(name.into());
self
}
fn detect_opportunities(&self, file: &PureFile) -> Vec<RwLockOpportunity> {
let mut opportunities = Vec::new();
for item in &file.items {
if let PureItem::Struct(s) = item {
if let Some(ref target) = self.target_struct {
if &s.name != target {
continue;
}
}
if let PureFields::Named(fields) = &s.fields {
for field in fields {
let type_str = match &field.ty {
PureType::Path(p) => p.as_str(),
_ => continue,
};
if type_str.contains("Mutex<")
&& (type_str.contains("HashMap")
|| type_str.contains("BTreeMap")
|| type_str.contains("Vec<")
|| type_str.contains("HashSet")
|| field.name.to_lowercase().contains("cache")
|| field.name.to_lowercase().contains("registry")
|| field.name.to_lowercase().contains("store"))
{
opportunities.push(RwLockOpportunity {
struct_name: s.name.clone(),
field_name: field.name.clone(),
});
}
}
}
}
}
opportunities
}
}
#[derive(Debug)]
struct RwLockOpportunity {
struct_name: String,
field_name: String,
}
impl Mutation for UseRwLockMutation {
fn describe(&self) -> String {
"Replace Mutex with RwLock for read-heavy data structures".to_string()
}
fn mutation_type(&self) -> &'static str {
"UseRwLock"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
impl Detect for UseRwLockMutation {
fn detect(&self, file: &PureFile) -> Vec<DetectOpportunity> {
self.detect_opportunities(file)
.into_iter()
.map(|o| {
DetectOpportunity::new(
DetectLocation::struct_item(&o.struct_name),
format!(
"Consider using RwLock for field '{}' if reads outnumber writes",
o.field_name
),
)
.with_operations(vec![DetectOperation::Refactor])
.with_confidence(0.5)
.with_context(format!("field:{}", o.field_name))
})
.collect()
}
fn category(&self) -> DetectCategory {
DetectCategory::Performance
}
fn detect_name(&self) -> &'static str {
"UseRwLock"
}
fn detect_description(&self) -> &str {
"Replace Mutex with RwLock for read-heavy data structures"
}
}
#[derive(Debug, Clone, Default)]
pub struct LockScopeMutation {
pub target_fn: Option<SymbolId>,
}
impl LockScopeMutation {
pub fn new() -> Self {
Self::default()
}
pub fn for_fn(mut self, id: SymbolId) -> Self {
self.target_fn = Some(id);
self
}
fn detect_opportunities(&self, file: &PureFile) -> Vec<LockScopeOpportunity> {
let mut opportunities = Vec::new();
for item in &file.items {
if let PureItem::Impl(impl_block) = item {
for impl_item in &impl_block.items {
if let ryo_source::pure::PureImplItem::Fn(func) = impl_item {
if func.is_async {
let body_str = format!("{:?}", func.body);
if body_str.contains("lock()")
&& (body_str.contains(".await") || body_str.contains("await"))
{
opportunities.push(LockScopeOpportunity {
impl_type: impl_block.self_ty.clone(),
fn_name: func.name.clone(),
issue: "lock_across_await".to_string(),
});
}
}
}
}
}
}
opportunities
}
}
#[derive(Debug)]
struct LockScopeOpportunity {
impl_type: String,
fn_name: String,
issue: String,
}
impl Mutation for LockScopeMutation {
fn describe(&self) -> String {
"Detect locks held across await points or with unnecessarily wide scope".to_string()
}
fn mutation_type(&self) -> &'static str {
"LockScope"
}
fn box_clone(&self) -> Box<dyn Mutation> {
Box::new(self.clone())
}
}
impl Detect for LockScopeMutation {
fn detect(&self, file: &PureFile) -> Vec<DetectOpportunity> {
self.detect_opportunities(file)
.into_iter()
.map(|o| {
DetectOpportunity::new(
DetectLocation::fn_item(&o.fn_name),
format!(
"Async method '{}::{}' may hold lock across await point",
o.impl_type, o.fn_name
),
)
.with_operations(vec![DetectOperation::Refactor])
.with_confidence(0.6)
.with_context(o.issue)
})
.collect()
}
fn category(&self) -> DetectCategory {
DetectCategory::Safety
}
fn detect_name(&self) -> &'static str {
"LockScope"
}
fn detect_description(&self) -> &str {
"Detect locks held across await points or with unnecessarily wide scope"
}
}