use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use proc_macro2::{Group, TokenStream, TokenTree};
use quote::ToTokens;
use syn::visit_mut::VisitMut;
pub(crate) fn strip_trailing_commas(stream: TokenStream) -> TokenStream {
stream
.into_iter()
.map(|tree| match tree {
TokenTree::Group(group) => {
let inner = strip_last_comma(strip_trailing_commas(group.stream()));
let mut rebuilt = Group::new(group.delimiter(), inner);
rebuilt.set_span(group.span());
TokenTree::Group(rebuilt)
}
other => other,
})
.collect()
}
fn strip_last_comma(stream: TokenStream) -> TokenStream {
let mut tokens: Vec<TokenTree> = stream.into_iter().collect();
if tokens.last().is_some_and(is_comma) {
tokens.pop();
}
tokens.into_iter().collect()
}
fn is_comma(tree: &TokenTree) -> bool {
matches!(tree, TokenTree::Punct(p) if p.as_char() == ',')
}
pub(crate) fn canonical_literals(stream: TokenStream) -> TokenStream {
let mut out = Vec::new();
let mut opaque = false;
for tree in stream {
let tree = match tree {
TokenTree::Literal(lit) => {
let span = lit.span();
match syn::parse_str::<syn::Lit>(&lit.to_string()) {
Ok(parsed) => canon_one_lit(parsed, span),
Err(_) => TokenTree::Literal(lit),
}
}
TokenTree::Group(group) if !opaque => {
let span = group.span();
let mut rebuilt = Group::new(group.delimiter(), canonical_literals(group.stream()));
rebuilt.set_span(span);
TokenTree::Group(rebuilt)
}
other => other,
};
opaque = matches!(&tree, TokenTree::Punct(p) if matches!(p.as_char(), '!' | '#'));
out.push(tree);
}
out.into_iter().collect()
}
fn canon_one_lit(lit: syn::Lit, span: proc_macro2::Span) -> TokenTree {
let rebuilt = match lit {
syn::Lit::Int(i) => {
let s = format!("{}{}", i.base10_digits(), i.suffix());
syn::Lit::Int(syn::LitInt::new(&s, span))
}
syn::Lit::Float(f) => {
let digits = canon_float_str(f.base10_digits());
syn::Lit::Float(syn::LitFloat::new(&format!("{digits}{}", f.suffix()), span))
}
syn::Lit::Str(s) => syn::Lit::Str(syn::LitStr::new(&s.value(), span)),
syn::Lit::ByteStr(b) => syn::Lit::ByteStr(syn::LitByteStr::new(&b.value(), span)),
syn::Lit::CStr(c) => syn::Lit::CStr(syn::LitCStr::new(c.value().as_c_str(), span)),
syn::Lit::Char(c) => syn::Lit::Char(syn::LitChar::new(c.value(), span)),
syn::Lit::Byte(b) => syn::Lit::Byte(syn::LitByte::new(b.value(), span)),
other => other,
};
rebuilt
.to_token_stream()
.into_iter()
.next()
.expect("rebuilt literal produces at least one token")
}
fn canon_float_str(s: &str) -> String {
let (mantissa, exponent) = match s.split_once('e') {
Some((mantissa, exponent)) => (mantissa, Some(exponent)),
None => (s, None),
};
let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, ""));
let fraction = fraction.trim_end_matches('0');
let mut out = String::with_capacity(s.len());
out.push_str(whole);
out.push('.');
out.push_str(if fraction.is_empty() { "0" } else { fraction });
if let Some(exponent) = exponent {
out.push('e');
let (sign, digits) = exponent
.strip_prefix('-')
.map_or(("", exponent), |digits| ("-", digits));
out.push_str(sign);
let digits = digits.trim_start_matches('0');
out.push_str(if digits.is_empty() { "0" } else { digits });
}
out
}
pub(crate) fn normalize_file(file: &mut syn::File) {
Drift.visit_file_mut(file);
}
struct Drift;
impl VisitMut for Drift {
#[expect(
clippy::result_large_err,
reason = "a non-use entry goes back to the list"
)]
fn visit_file_mut(&mut self, file: &mut syn::File) {
hoist_uses(
&mut file.items,
|item| match item {
syn::Item::Use(u) => Ok(u),
other => Err(other),
},
syn::Item::Use,
);
syn::visit_mut::visit_file_mut(self, file);
}
#[expect(
clippy::result_large_err,
reason = "a non-use entry goes back to the list"
)]
fn visit_block_mut(&mut self, block: &mut syn::Block) {
hoist_uses(
&mut block.stmts,
|stmt| match stmt {
syn::Stmt::Item(syn::Item::Use(u)) => Ok(u),
other => Err(other),
},
|u| syn::Stmt::Item(syn::Item::Use(u)),
);
hoist_block_items(&mut block.stmts);
semicolon_non_tail_macros(&mut block.stmts);
syn::visit_mut::visit_block_mut(self, block);
drop_empty_stmts(&mut block.stmts);
fold_tail_return(&mut block.stmts);
}
fn visit_arm_mut(&mut self, arm: &mut syn::Arm) {
unwrap_arm_block(arm);
syn::visit_mut::visit_arm_mut(self, arm);
}
fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
syn::visit_mut::visit_expr_mut(self, expr);
fold_paren_expr(expr);
if let syn::Expr::Closure(closure) = expr {
unwrap_single_expr_block(&mut closure.body);
}
}
fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
syn::visit_mut::visit_pat_mut(self, pat);
fold_paren_pat(pat);
}
fn visit_type_mut(&mut self, ty: &mut syn::Type) {
syn::visit_mut::visit_type_mut(self, ty);
fold_paren_type(ty);
}
fn visit_signature_mut(&mut self, sig: &mut syn::Signature) {
syn::visit_mut::visit_signature_mut(self, sig);
fold_unit_return(&mut sig.output);
}
fn visit_stmt_mut(&mut self, stmt: &mut syn::Stmt) {
match stmt {
syn::Stmt::Macro(v) => strip_inert_attrs(&mut v.attrs),
syn::Stmt::Local(v) => strip_inert_attrs(&mut v.attrs),
_ => {}
}
syn::visit_mut::visit_stmt_mut(self, stmt);
}
fn visit_item_mut(&mut self, item: &mut syn::Item) {
if let Some(attrs) = item_attrs(item) {
strip_inert_attrs(attrs);
}
syn::visit_mut::visit_item_mut(self, item);
}
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
match item {
syn::ImplItem::Const(v) => strip_inert_attrs(&mut v.attrs),
syn::ImplItem::Fn(v) => strip_inert_attrs(&mut v.attrs),
syn::ImplItem::Type(v) => strip_inert_attrs(&mut v.attrs),
syn::ImplItem::Macro(v) => strip_inert_attrs(&mut v.attrs),
_ => {}
}
syn::visit_mut::visit_impl_item_mut(self, item);
}
fn visit_trait_item_mut(&mut self, item: &mut syn::TraitItem) {
match item {
syn::TraitItem::Const(v) => strip_inert_attrs(&mut v.attrs),
syn::TraitItem::Fn(v) => strip_inert_attrs(&mut v.attrs),
syn::TraitItem::Type(v) => strip_inert_attrs(&mut v.attrs),
syn::TraitItem::Macro(v) => strip_inert_attrs(&mut v.attrs),
_ => {}
}
syn::visit_mut::visit_trait_item_mut(self, item);
}
fn visit_field_mut(&mut self, field: &mut syn::Field) {
strip_inert_attrs(&mut field.attrs);
syn::visit_mut::visit_field_mut(self, field);
}
fn visit_variant_mut(&mut self, variant: &mut syn::Variant) {
strip_inert_attrs(&mut variant.attrs);
syn::visit_mut::visit_variant_mut(self, variant);
}
fn visit_expr_macro_mut(&mut self, node: &mut syn::ExprMacro) {
normalize_macro_delim(&mut node.mac);
syn::visit_mut::visit_expr_macro_mut(self, node);
}
fn visit_stmt_macro_mut(&mut self, node: &mut syn::StmtMacro) {
normalize_macro_delim(&mut node.mac);
syn::visit_mut::visit_stmt_macro_mut(self, node);
}
fn visit_type_macro_mut(&mut self, node: &mut syn::TypeMacro) {
normalize_macro_delim(&mut node.mac);
syn::visit_mut::visit_type_macro_mut(self, node);
}
fn visit_item_macro_mut(&mut self, node: &mut syn::ItemMacro) {
if node.ident.is_none() {
normalize_macro_delim(&mut node.mac);
node.semi_token.get_or_insert_with(Default::default);
}
syn::visit_mut::visit_item_macro_mut(self, node);
}
fn visit_impl_item_macro_mut(&mut self, node: &mut syn::ImplItemMacro) {
normalize_macro_delim(&mut node.mac);
node.semi_token.get_or_insert_with(Default::default);
syn::visit_mut::visit_impl_item_macro_mut(self, node);
}
fn visit_trait_item_macro_mut(&mut self, node: &mut syn::TraitItemMacro) {
normalize_macro_delim(&mut node.mac);
node.semi_token.get_or_insert_with(Default::default);
syn::visit_mut::visit_trait_item_macro_mut(self, node);
}
}
fn unwrap_arm_block(arm: &mut syn::Arm) {
unwrap_single_expr_block(&mut arm.body);
}
fn unwrap_single_expr_block(body: &mut Box<syn::Expr>) {
let syn::Expr::Block(block) = body.as_mut() else {
return;
};
if block.label.is_some()
|| !block.attrs.is_empty()
|| !matches!(block.block.stmts.as_slice(), [syn::Stmt::Expr(_, None)])
{
return;
}
if let Some(syn::Stmt::Expr(expr, None)) = block.block.stmts.pop() {
**body = expr;
}
}
fn drop_empty_stmts(stmts: &mut Vec<syn::Stmt>) {
stmts.retain(|stmt| {
!matches!(
stmt,
syn::Stmt::Expr(syn::Expr::Verbatim(v), Some(_)) if v.is_empty()
)
});
}
fn fold_tail_return(stmts: &mut Vec<syn::Stmt>) {
let n = stmts.len();
if n == 0 {
return;
}
let is_valued_tail_return =
matches!(&stmts[n - 1], syn::Stmt::Expr(syn::Expr::Return(r), Some(_)) if r.expr.is_some());
if !is_valued_tail_return {
return;
}
let last = stmts.remove(n - 1);
if let syn::Stmt::Expr(syn::Expr::Return(mut ret), Some(_)) = last
&& let Some(inner) = ret.expr.take()
{
stmts.push(syn::Stmt::Expr(*inner, None));
}
}
fn semicolon_non_tail_macros(stmts: &mut [syn::Stmt]) {
let Some((_, rest)) = stmts.split_last_mut() else {
return;
};
for stmt in rest {
if let syn::Stmt::Macro(mac) = stmt {
mac.semi_token.get_or_insert_with(Default::default);
}
}
}
fn fold_unit_return(output: &mut syn::ReturnType) {
if let syn::ReturnType::Type(_, ty) = output
&& matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
{
*output = syn::ReturnType::Default;
}
}
fn fold_paren_expr(expr: &mut syn::Expr) {
if !matches!(expr, syn::Expr::Paren(p) if p.attrs.is_empty()) {
return;
}
let dummy = syn::Expr::Verbatim(proc_macro2::TokenStream::new());
if let syn::Expr::Paren(paren) = core::mem::replace(expr, dummy) {
*expr = *paren.expr;
}
}
fn fold_paren_pat(pat: &mut syn::Pat) {
if !matches!(pat, syn::Pat::Paren(p) if p.attrs.is_empty()) {
return;
}
let dummy = syn::Pat::Verbatim(proc_macro2::TokenStream::new());
if let syn::Pat::Paren(paren) = core::mem::replace(pat, dummy) {
*pat = *paren.pat;
}
}
fn fold_paren_type(ty: &mut syn::Type) {
if !matches!(ty, syn::Type::Paren(_)) {
return;
}
let dummy = syn::Type::Verbatim(proc_macro2::TokenStream::new());
if let syn::Type::Paren(paren) = core::mem::replace(ty, dummy) {
*ty = *paren.elem;
}
}
fn is_inert_attr(attr: &syn::Attribute) -> bool {
let p = attr.path();
p.is_ident("doc") || p.is_ident("allow") || p.is_ident("expect") || p.is_ident("warn")
}
fn strip_inert_attrs(attrs: &mut Vec<syn::Attribute>) {
attrs.retain(|attr| !is_inert_attr(attr));
}
fn item_attrs(item: &mut syn::Item) -> Option<&mut Vec<syn::Attribute>> {
Some(match item {
syn::Item::Const(v) => &mut v.attrs,
syn::Item::Enum(v) => &mut v.attrs,
syn::Item::ExternCrate(v) => &mut v.attrs,
syn::Item::Fn(v) => &mut v.attrs,
syn::Item::ForeignMod(v) => &mut v.attrs,
syn::Item::Impl(v) => &mut v.attrs,
syn::Item::Macro(v) => &mut v.attrs,
syn::Item::Mod(v) => &mut v.attrs,
syn::Item::Static(v) => &mut v.attrs,
syn::Item::Struct(v) => &mut v.attrs,
syn::Item::Trait(v) => &mut v.attrs,
syn::Item::TraitAlias(v) => &mut v.attrs,
syn::Item::Type(v) => &mut v.attrs,
syn::Item::Union(v) => &mut v.attrs,
syn::Item::Use(v) => &mut v.attrs,
_ => return None,
})
}
fn hoist_uses<T>(
list: &mut Vec<T>,
into_use: impl Fn(T) -> Result<syn::ItemUse, T>,
wrap: impl Fn(syn::ItemUse) -> T,
) {
let mut leaves = Vec::new();
let rest: Vec<T> = core::mem::take(list)
.into_iter()
.filter_map(|entry| {
let item = match into_use(entry) {
Ok(item) => item,
Err(entry) => return Some(entry),
};
let mut trees = Vec::new();
flatten_use_tree(&[], item.tree, &mut trees);
leaves.extend(trees.into_iter().map(|tree| syn::ItemUse {
attrs: item.attrs.clone(),
vis: item.vis.clone(),
use_token: item.use_token,
leading_colon: item.leading_colon,
tree,
semi_token: item.semi_token,
}));
None
})
.collect();
leaves.sort_by_cached_key(|leaf| {
format!(
"{} {}",
leaf.vis.to_token_stream(),
leaf.tree.to_token_stream()
)
});
list.extend(leaves.into_iter().map(wrap));
list.extend(rest);
}
fn item_has_only_inert_attrs(item: &syn::Item) -> bool {
let attrs: &[syn::Attribute] = match item {
syn::Item::Const(v) => &v.attrs,
syn::Item::Enum(v) => &v.attrs,
syn::Item::ExternCrate(v) => &v.attrs,
syn::Item::Fn(v) => &v.attrs,
syn::Item::ForeignMod(v) => &v.attrs,
syn::Item::Impl(v) => &v.attrs,
syn::Item::Mod(v) => &v.attrs,
syn::Item::Static(v) => &v.attrs,
syn::Item::Struct(v) => &v.attrs,
syn::Item::Trait(v) => &v.attrs,
syn::Item::TraitAlias(v) => &v.attrs,
syn::Item::Type(v) => &v.attrs,
syn::Item::Union(v) => &v.attrs,
_ => return true,
};
attrs.iter().all(is_inert_attr)
}
fn hoist_block_items(stmts: &mut Vec<syn::Stmt>) {
let use_count = stmts.partition_point(|s| matches!(s, syn::Stmt::Item(syn::Item::Use(_))));
let rel_macro = stmts[use_count..]
.iter()
.position(|s| matches!(s, syn::Stmt::Item(syn::Item::Macro(_))));
let hoist_end = use_count + rel_macro.unwrap_or(stmts.len() - use_count);
if hoist_end == use_count {
return;
}
let tail = stmts.split_off(hoist_end);
let region = stmts.split_off(use_count);
let mut items = Vec::new();
let mut rest = Vec::new();
for stmt in region {
let hoistable = match &stmt {
syn::Stmt::Item(item) => item_has_only_inert_attrs(item),
_ => false,
};
if hoistable {
items.push(stmt);
} else {
rest.push(stmt);
}
}
stmts.extend(items);
stmts.extend(rest);
stmts.extend(tail);
}
fn flatten_use_tree(prefix: &[syn::Ident], tree: syn::UseTree, out: &mut Vec<syn::UseTree>) {
match tree {
syn::UseTree::Path(path) => {
let mut prefix = prefix.to_vec();
prefix.push(path.ident);
flatten_use_tree(&prefix, *path.tree, out);
}
syn::UseTree::Group(group) => {
for item in group.items {
flatten_use_tree(prefix, item, out);
}
}
syn::UseTree::Name(name) if name.ident == "self" => {
if let [head @ .., last] = prefix {
let leaf = syn::UseTree::Name(syn::UseName {
ident: last.clone(),
});
out.push(path_tree(head, leaf));
}
}
leaf => out.push(path_tree(prefix, leaf)),
}
}
fn path_tree(prefix: &[syn::Ident], leaf: syn::UseTree) -> syn::UseTree {
match prefix {
[] => leaf,
[head, rest @ ..] => syn::UseTree::Path(syn::UsePath {
ident: head.clone(),
colon2_token: syn::token::PathSep::default(),
tree: Box::new(path_tree(rest, leaf)),
}),
}
}
fn normalize_macro_delim(mac: &mut syn::Macro) {
if matches!(mac.delimiter, syn::MacroDelimiter::Paren(_)) {
return;
}
let span = *mac.delimiter.span();
mac.delimiter = syn::MacroDelimiter::Paren(syn::token::Paren { span });
}