use std::collections::BTreeSet;
use std::rc::Rc;
use syn::{Expr, FnArg, Signature, Type};
use crate::graph::extend_pairwise_edges;
use crate::ir::type_introspection::{
contains_refcounted_type, for_each_pat_ident, is_refcounted_type, is_string_type,
iter_expr_ident,
};
use super::syn_helpers::typed_ident_from_pat;
pub(super) struct FnSavedState {
fn_index: Option<usize>,
refcounted_bindings: BTreeSet<Box<str>>,
refcounted_containers: BTreeSet<Box<str>>,
string_bindings: BTreeSet<Box<str>>,
body_types: BTreeSet<Rc<str>>,
}
pub(super) struct FnScope {
current: Option<usize>,
refcounted_bindings: BTreeSet<Box<str>>,
refcounted_containers: BTreeSet<Box<str>>,
string_bindings: BTreeSet<Box<str>>,
body_types: BTreeSet<Rc<str>>,
body_type_names_buf: Vec<Rc<str>>,
}
impl FnScope {
pub(super) fn new() -> Self {
Self {
current: None,
refcounted_bindings: BTreeSet::new(),
refcounted_containers: BTreeSet::new(),
string_bindings: BTreeSet::new(),
body_types: BTreeSet::new(),
body_type_names_buf: Vec::new(),
}
}
pub(super) fn current(&self) -> Option<usize> {
self.current
}
pub(super) fn enter(&mut self, fn_index: usize) -> FnSavedState {
let saved = FnSavedState {
fn_index: self.current,
refcounted_bindings: std::mem::take(&mut self.refcounted_bindings),
refcounted_containers: std::mem::take(&mut self.refcounted_containers),
string_bindings: std::mem::take(&mut self.string_bindings),
body_types: std::mem::take(&mut self.body_types),
};
self.current = Some(fn_index);
saved
}
pub(super) fn leave(&mut self, saved: FnSavedState) {
self.current = saved.fn_index;
self.refcounted_bindings = saved.refcounted_bindings;
self.refcounted_containers = saved.refcounted_containers;
self.string_bindings = saved.string_bindings;
self.body_types = saved.body_types;
}
pub(super) fn is_refcounted(&self, name: &str) -> bool {
self.refcounted_bindings.contains(name)
}
pub(super) fn record_refcounted_params(&mut self, sig: &Signature) {
for input in &sig.inputs {
let FnArg::Typed(pt) = input else { continue };
let syn::Pat::Ident(pi) = pt.pat.as_ref() else {
continue;
};
self.classify_refcounted_ident(&pi.ident, &pt.ty);
}
}
fn classify_refcounted_ident(&mut self, ident: &syn::Ident, ty: &Type) {
match (is_refcounted_type(ty), contains_refcounted_type(ty)) {
(true, _) => {
self.refcounted_bindings
.insert(ident.to_string().into_boxed_str());
}
(false, true) => {
self.refcounted_containers
.insert(ident.to_string().into_boxed_str());
}
_ => {}
}
}
pub(super) fn record_refcounted_from_pat(&mut self, pat: &syn::Pat) {
let Some((ident, ty)) = typed_ident_from_pat(pat) else {
return;
};
self.classify_refcounted_ident(ident, ty);
}
pub(super) fn record_string_binding(&mut self, pat: &syn::Pat) {
let Some((ident, ty)) = typed_ident_from_pat(pat) else {
return;
};
if !is_string_type(ty) {
return;
}
self.string_bindings
.insert(ident.to_string().into_boxed_str());
}
pub(super) fn record_refcounted_loop_bindings(&mut self, pat: &syn::Pat, iter_expr: &Expr) {
let Some(ident) = iter_expr_ident(iter_expr) else {
return;
};
if !self.refcounted_containers.iter().any(|s| ident == &**s) {
return;
}
for_each_pat_ident(pat, &mut |name| {
self.refcounted_bindings.insert(name);
});
}
pub(super) fn is_write_macro_to_string(&self, expr: &Expr) -> bool {
let Expr::Macro(expr_macro) = expr else {
return false;
};
let is_write = expr_macro
.mac
.path
.segments
.last()
.is_some_and(|s| s.ident == "write" || s.ident == "writeln");
if !is_write {
return false;
}
let tokens = &expr_macro.mac.tokens;
let first_ident = tokens.clone().into_iter().next();
match first_ident {
Some(proc_macro2::TokenTree::Ident(ident)) => {
self.string_bindings.iter().any(|s| ident == **s)
}
_ => false,
}
}
pub(super) fn collect_body_type(&mut self, path: &syn::Path) {
if self.current.is_none() {
return;
}
let Some(seg) = path.segments.last() else {
return;
};
let ident_str = seg.ident.to_string();
if self.body_types.contains(ident_str.as_str()) {
return;
}
self.body_types.insert(Rc::from(ident_str));
}
pub(super) fn body_type_edges(&mut self) -> Box<[(Rc<str>, Rc<str>)]> {
self.body_type_names_buf.clear();
self.body_type_names_buf
.extend(self.body_types.iter().cloned());
let mut edges = Vec::new();
extend_pairwise_edges(&self.body_type_names_buf, &mut edges);
edges.into_boxed_slice()
}
}