use std::collections::{HashMap, HashSet};
use syn::FnArg;
#[derive(Debug, Clone)]
pub struct ScopeTracker {
local_vars: HashSet<String>,
params: HashMap<String, ParameterInfo>,
scope_depth: usize,
}
#[derive(Debug, Clone)]
pub struct ParameterInfo {
pub name: String,
pub is_mut: bool,
pub is_self: bool,
pub self_kind: Option<SelfKind>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelfKind {
Ref,
MutRef,
Owned,
MutOwned,
}
impl ScopeTracker {
pub fn new() -> Self {
Self {
local_vars: HashSet::new(),
params: HashMap::new(),
scope_depth: 0,
}
}
pub fn add_parameter(&mut self, param: &FnArg) {
match param {
FnArg::Receiver(receiver) => {
let kind = match (&receiver.reference, &receiver.mutability) {
(Some(_), Some(_)) => SelfKind::MutRef, (Some(_), None) => SelfKind::Ref, (None, Some(_)) => SelfKind::MutOwned, (None, None) => SelfKind::Owned, };
self.params.insert(
"self".to_string(),
ParameterInfo {
name: "self".to_string(),
is_mut: receiver.mutability.is_some(),
is_self: true,
self_kind: Some(kind),
},
);
}
FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(ident) = &*pat_type.pat {
self.params.insert(
ident.ident.to_string(),
ParameterInfo {
name: ident.ident.to_string(),
is_mut: ident.mutability.is_some(),
is_self: false,
self_kind: None,
},
);
}
}
}
}
pub fn add_local_var(&mut self, name: String) {
self.local_vars.insert(name);
}
pub fn is_local(&self, name: &str) -> bool {
self.local_vars.contains(name) || self.params.get(name).is_some_and(|p| !p.is_self)
}
pub fn is_self(&self, name: &str) -> bool {
self.params.get(name).is_some_and(|p| p.is_self)
}
pub fn get_self_kind(&self) -> Option<SelfKind> {
self.params.get("self").and_then(|p| p.self_kind)
}
pub fn enter_scope(&mut self) {
self.scope_depth += 1;
}
pub fn exit_scope(&mut self) {
self.scope_depth = self.scope_depth.saturating_sub(1);
}
}
impl Default for ScopeTracker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_new_tracker_is_empty() {
let tracker = ScopeTracker::new();
assert!(!tracker.is_local("x"));
assert!(!tracker.is_self("self"));
assert_eq!(tracker.get_self_kind(), None);
}
#[test]
fn test_add_regular_parameter() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(x: i32);
tracker.add_parameter(¶m);
assert!(tracker.is_local("x"));
assert!(!tracker.is_self("x"));
}
#[test]
fn test_add_mut_parameter() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(mut x: i32);
tracker.add_parameter(¶m);
assert!(tracker.is_local("x"));
}
#[test]
fn test_self_ref() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(&self);
tracker.add_parameter(¶m);
assert!(tracker.is_self("self"));
assert_eq!(tracker.get_self_kind(), Some(SelfKind::Ref));
}
#[test]
fn test_self_mut_ref() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(&mut self);
tracker.add_parameter(¶m);
assert!(tracker.is_self("self"));
assert_eq!(tracker.get_self_kind(), Some(SelfKind::MutRef));
}
#[test]
fn test_self_owned() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(self);
tracker.add_parameter(¶m);
assert!(tracker.is_self("self"));
assert_eq!(tracker.get_self_kind(), Some(SelfKind::Owned));
}
#[test]
fn test_self_mut_owned() {
let mut tracker = ScopeTracker::new();
let param: FnArg = parse_quote!(mut self);
tracker.add_parameter(¶m);
assert!(tracker.is_self("self"));
assert_eq!(tracker.get_self_kind(), Some(SelfKind::MutOwned));
}
#[test]
fn test_add_local_variable() {
let mut tracker = ScopeTracker::new();
tracker.add_local_var("result".to_string());
assert!(tracker.is_local("result"));
assert!(!tracker.is_self("result"));
}
#[test]
fn test_scope_depth() {
let mut tracker = ScopeTracker::new();
assert_eq!(tracker.scope_depth, 0);
tracker.enter_scope();
assert_eq!(tracker.scope_depth, 1);
tracker.enter_scope();
assert_eq!(tracker.scope_depth, 2);
tracker.exit_scope();
assert_eq!(tracker.scope_depth, 1);
tracker.exit_scope();
assert_eq!(tracker.scope_depth, 0);
}
}