use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::String;
use alloc::vec::Vec;
use syn::visit_mut::VisitMut;
use crate::alpha::Renamer;
use crate::scope::ident_name;
pub(crate) fn normalize(renamer: &Renamer<'_>, sig: &syn::Signature, block: &mut syn::Block) {
let mut claims = Claims::default();
syn::visit::Visit::visit_block(&mut claims, block);
let unclaimed = |name: &str| !claims.names.contains(name) && renamer.type_unclaimed(name);
let mut fold = Fold {
vectors: !claims.glob && unclaimed("Vec"),
strings: !claims.glob && unclaimed("String"),
slices: !claims.glob && unclaimed("str"),
scopes: alloc::vec![BTreeMap::new()],
};
for input in &sig.inputs {
if let syn::FnArg::Typed(typed) = input {
let kind = fold.collection(&typed.ty);
fold.bind(&typed.pat, kind);
}
}
fold.visit_block_mut(block);
}
#[derive(Default)]
struct Claims {
names: BTreeSet<String>,
glob: bool,
}
impl syn::visit::Visit<'_> for Claims {
fn visit_item(&mut self, item: &syn::Item) {
if let Some(ident) = crate::alpha::type_item_ident(item) {
self.names.insert(ident_name(ident));
}
syn::visit::visit_item(self, item);
}
fn visit_use_name(&mut self, name: &syn::UseName) {
self.names.insert(ident_name(&name.ident));
}
fn visit_use_rename(&mut self, rename: &syn::UseRename) {
self.names.insert(ident_name(&rename.rename));
}
fn visit_use_glob(&mut self, _: &syn::UseGlob) {
self.glob = true;
}
fn visit_type_param(&mut self, param: &syn::TypeParam) {
self.names.insert(ident_name(¶m.ident));
syn::visit::visit_type_param(self, param);
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Vec,
Array,
Slice,
String,
Str,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Access {
Owned,
Shared,
Mut,
}
#[derive(Clone, Copy)]
struct Collection {
kind: Kind,
access: Access,
}
struct Fold {
vectors: bool,
strings: bool,
slices: bool,
scopes: Vec<BTreeMap<String, Option<Collection>>>,
}
impl Fold {
fn collection(&self, ty: &syn::Type) -> Option<Collection> {
let (access, ty) = match ty {
syn::Type::Reference(reference) if reference.mutability.is_some() => {
(Access::Mut, reference.elem.as_ref())
}
syn::Type::Reference(reference) => (Access::Shared, reference.elem.as_ref()),
ty => (Access::Owned, ty),
};
let kind = match ty {
syn::Type::Array(_) => Kind::Array,
syn::Type::Slice(_) => Kind::Slice,
syn::Type::Path(path) if path.qself.is_none() && path.path.leading_colon.is_none() => {
let [segment] = path.path.segments.iter().collect::<Vec<_>>()[..] else {
return None;
};
match (ident_name(&segment.ident).as_str(), &segment.arguments) {
("Vec", syn::PathArguments::AngleBracketed(args))
if self.vectors && args.args.len() == 1 =>
{
Kind::Vec
}
("String", syn::PathArguments::None) if self.strings => Kind::String,
("str", syn::PathArguments::None) if self.slices => Kind::Str,
_ => return None,
}
}
_ => return None,
};
Some(Collection { kind, access })
}
fn bind(&mut self, pat: &syn::Pat, kind: Option<Collection>) {
struct Names<'s>(
&'s mut BTreeMap<String, Option<Collection>>,
Option<Collection>,
);
impl syn::visit::Visit<'_> for Names<'_> {
fn visit_pat_ident(&mut self, ident: &syn::PatIdent) {
let kind = self
.1
.filter(|_| ident.by_ref.is_none() && ident.subpat.is_none());
self.0.insert(ident_name(&ident.ident), kind);
syn::visit::visit_pat_ident(self, ident);
}
fn visit_expr(&mut self, _: &syn::Expr) {}
}
let kind = kind.filter(|_| matches!(pat, syn::Pat::Ident(_)));
let scope = self.scopes.last_mut().expect("a scope");
syn::visit::Visit::visit_pat(&mut Names(scope, kind), pat);
}
fn binding(&self, expr: &syn::Expr) -> Option<Collection> {
let syn::Expr::Path(path) = expr else {
return None;
};
let name = ident_name(path.path.get_ident().filter(|_| path.qself.is_none())?);
self.scopes
.iter()
.rev()
.find_map(|scope| scope.get(&name))
.copied()
.flatten()
}
fn iterable(&self, expr: &mut syn::Expr) {
let syn::Expr::MethodCall(call) = expr else {
return;
};
if !call.attrs.is_empty() || call.turbofish.is_some() || !call.args.is_empty() {
return;
}
let Some(collection) = self.binding(&call.receiver) else {
return;
};
if !matches!(collection.kind, Kind::Vec | Kind::Array | Kind::Slice) {
return;
}
let borrow = match (ident_name(&call.method).as_str(), collection.access) {
("iter", Access::Owned) => Some(false),
("iter_mut", Access::Owned) => Some(true),
("iter", Access::Shared) | ("iter_mut", Access::Mut) => None,
("into_iter", Access::Owned) if collection.kind == Kind::Vec => None,
_ => return,
};
let receiver = core::mem::replace(
call.receiver.as_mut(),
syn::Expr::Verbatim(proc_macro2::TokenStream::new()),
);
*expr = match borrow {
Some(mutable) => syn::Expr::Reference(syn::ExprReference {
attrs: Vec::new(),
and_token: syn::token::And::default(),
mutability: mutable.then(syn::token::Mut::default),
expr: Box::new(receiver),
}),
None => receiver,
};
}
fn emptiness(&self, expr: &mut syn::Expr) {
let syn::Expr::Binary(binary) = expr else {
return;
};
let (empty, receiver) = if let Some(receiver) = self.length(&binary.left)
&& let Some(literal) = small_literal(&binary.right)
{
match (binary.op, literal) {
(syn::BinOp::Eq(_), 0) | (syn::BinOp::Lt(_), 1) => (true, receiver),
(syn::BinOp::Ne(_) | syn::BinOp::Gt(_), 0) | (syn::BinOp::Ge(_), 1) => {
(false, receiver)
}
_ => return,
}
} else if let Some(receiver) = self.length(&binary.right)
&& let Some(literal) = small_literal(&binary.left)
{
match (binary.op, literal) {
(syn::BinOp::Eq(_), 0) | (syn::BinOp::Gt(_), 1) => (true, receiver),
(syn::BinOp::Ne(_) | syn::BinOp::Lt(_), 0) | (syn::BinOp::Le(_), 1) => {
(false, receiver)
}
_ => return,
}
} else {
return;
};
let test = syn::Expr::MethodCall(syn::ExprMethodCall {
attrs: Vec::new(),
receiver: Box::new(receiver),
dot_token: syn::token::Dot::default(),
method: syn::Ident::new("is_empty", proc_macro2::Span::call_site()),
turbofish: None,
paren_token: syn::token::Paren::default(),
args: syn::punctuated::Punctuated::new(),
});
*expr = if empty {
test
} else {
syn::Expr::Unary(syn::ExprUnary {
attrs: Vec::new(),
op: syn::UnOp::Not(syn::token::Not::default()),
expr: Box::new(test),
})
};
}
fn length(&self, expr: &syn::Expr) -> Option<syn::Expr> {
let syn::Expr::MethodCall(call) = expr else {
return None;
};
(call.attrs.is_empty()
&& call.turbofish.is_none()
&& call.args.is_empty()
&& call.method == "len"
&& self.binding(&call.receiver).is_some())
.then(|| call.receiver.as_ref().clone())
}
fn scoped(&mut self, visit: impl FnOnce(&mut Self)) {
self.scopes.push(BTreeMap::new());
visit(self);
self.scopes.pop();
}
}
fn small_literal(expr: &syn::Expr) -> Option<u8> {
let syn::Expr::Lit(syn::ExprLit {
attrs,
lit: syn::Lit::Int(int),
}) = expr
else {
return None;
};
match (
attrs.is_empty() && matches!(int.suffix(), "" | "usize"),
int.base10_digits(),
) {
(true, "0") => Some(0),
(true, "1") => Some(1),
_ => None,
}
}
impl VisitMut for Fold {
fn visit_item_mut(&mut self, _: &mut syn::Item) {}
fn visit_block_mut(&mut self, block: &mut syn::Block) {
self.scoped(|fold| {
for stmt in &mut block.stmts {
fold.visit_stmt_mut(stmt);
}
});
}
fn visit_local_mut(&mut self, local: &mut syn::Local) {
if let Some(init) = &mut local.init {
self.visit_expr_mut(&mut init.expr);
if let Some((_, diverge)) = &mut init.diverge {
self.visit_expr_mut(diverge);
}
}
let (pat, kind) = match &local.pat {
syn::Pat::Type(typed) if local.attrs.is_empty() => {
(typed.pat.as_ref(), self.collection(&typed.ty))
}
pat => (pat, None),
};
self.bind(pat, kind);
}
fn visit_expr_closure_mut(&mut self, closure: &mut syn::ExprClosure) {
self.scoped(|fold| {
for input in &closure.inputs {
let (pat, kind) = match input {
syn::Pat::Type(typed) => (typed.pat.as_ref(), fold.collection(&typed.ty)),
pat => (pat, None),
};
fold.bind(pat, kind);
}
fold.visit_expr_mut(&mut closure.body);
});
}
fn visit_arm_mut(&mut self, arm: &mut syn::Arm) {
self.scoped(|fold| {
fold.bind(&arm.pat, None);
if let syn::Pat::Guard(guard) = &mut arm.pat {
fold.visit_expr_mut(&mut guard.guard);
}
fold.visit_expr_mut(&mut arm.body);
});
}
fn visit_expr_for_loop_mut(&mut self, expr: &mut syn::ExprForLoop) {
self.iterable(&mut expr.expr);
self.visit_expr_mut(&mut expr.expr);
self.scoped(|fold| {
fold.bind(&expr.pat, None);
fold.visit_block_mut(&mut expr.body);
});
}
fn visit_expr_if_mut(&mut self, branch: &mut syn::ExprIf) {
self.scoped(|fold| {
fold.condition(&mut branch.cond);
fold.visit_block_mut(&mut branch.then_branch);
});
if let Some((_, otherwise)) = &mut branch.else_branch {
self.visit_expr_mut(otherwise);
}
}
fn visit_expr_while_mut(&mut self, expr: &mut syn::ExprWhile) {
self.scoped(|fold| {
fold.condition(&mut expr.cond);
fold.visit_block_mut(&mut expr.body);
});
}
fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
if !crate::drift::expr_attrs(expr).is_empty() {
return;
}
syn::visit_mut::visit_expr_mut(self, expr);
self.emptiness(expr);
}
}
impl Fold {
fn condition(&mut self, cond: &mut syn::Expr) {
match cond {
syn::Expr::Let(binding) => {
self.visit_expr_mut(&mut binding.expr);
self.bind(&binding.pat, None);
}
syn::Expr::Binary(binary) if matches!(binary.op, syn::BinOp::And(_)) => {
self.condition(&mut binary.left);
self.condition(&mut binary.right);
}
other => self.visit_expr_mut(other),
}
}
}