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 map_group(group: &Group, f: impl FnOnce(TokenStream) -> TokenStream) -> TokenTree {
let mut rebuilt = Group::new(group.delimiter(), f(group.stream()));
rebuilt.set_span(group.span());
TokenTree::Group(rebuilt)
}
pub(crate) fn canonical_literal(lit: proc_macro2::Literal) -> TokenTree {
let span = lit.span();
let rebuilt = match syn::Lit::new(lit) {
syn::Lit::Int(i) => {
let decimal = format!("{}{}", i.base10_digits(), i.suffix());
match syn::parse_str::<syn::LitInt>(&decimal) {
Ok(mut read)
if read.base10_digits() == i.base10_digits() && read.suffix() == i.suffix() =>
{
read.set_span(span);
syn::Lit::Int(read)
}
_ => syn::Lit::Int(i),
}
}
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,
compiles: bool,
helpers: &mut crate::helpers::HelperSyntax,
observed: bool,
std_names: bool,
) {
if compiles {
if !observed {
crate::const_items::inline_array_constants(file);
}
helpers.retain_file(file);
}
if observed {
ObservedUses {
helpers: Some(&mut *helpers),
}
.visit_file_mut(file);
helpers.retain_file(file);
return;
}
Drift {
compiles,
std_names,
helpers: Some(&mut *helpers),
..Drift::default()
}
.visit_file_mut(file);
helpers.retain_file(file);
}
pub(crate) fn normalize_block(block: &mut syn::Block, frames: &[crate::scope::Frame]) {
Drift::within(frames).visit_block_mut(block);
}
pub(crate) fn normalize_arm(arm: &mut syn::Arm, frames: &[crate::scope::Frame]) {
Drift::within(frames).visit_arm_mut(arm);
}
pub(crate) fn normalize_expr(expr: &mut syn::Expr, frames: &[crate::scope::Frame]) {
Drift::within(frames).visit_expr_mut(expr);
}
fn frames_rebound(frames: &[crate::scope::Frame]) -> u64 {
use crate::scope::Ns;
let imported = frames
.iter()
.fold(0, |rebound, frame| rebound | frame.std_rebound);
STD_DERIVES
.into_iter()
.chain(crate::COMMA_BLIND_MACROS)
.map(|name| (Ns::Macro, name))
.chain(CRATE_ROOTS.map(|name| (Ns::Type, name)))
.filter(|&(ns, name)| frames.iter().any(|frame| frame.lookup(ns, name).is_some()))
.fold(imported, |rebound, (_, name)| rebound | std_name_bit(name))
}
#[derive(Default)]
struct Drift<'a> {
compiles: bool,
in_impl_header: bool,
std_names: bool,
file_rebound: u64,
std_scopes: Vec<StdScope>,
generic_names: Vec<(bool, String)>,
helpers: Option<&'a mut crate::helpers::HelperSyntax>,
}
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) {
if has_macro_attribute(&file.attrs) {
self.observed().visit_file_mut(file);
return;
}
self.file_rebound = file_rebound(file);
let mut root = StdScope::of(file.items.iter(), true);
if !self.std_names {
root.rebound = StdScope::ALL;
}
hoist_uses(
&mut file.items,
|item| match item {
syn::Item::Use(u) => Ok(u),
other => Err(other),
},
syn::Item::Use,
);
self.std_scopes.push(root);
syn::visit_mut::visit_file_mut(self, file);
self.std_scopes.pop();
}
#[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) {
let handles = self
.helpers
.as_mut()
.map(|helpers| helpers.take_block(block));
drop_empty_stmts(&mut block.stmts);
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);
if self.compiles {
drop_block_statement_semicolons(&mut block.stmts);
}
if let Some(handles) = handles {
self.helpers
.as_mut()
.expect("tracked helper syntax")
.put_block(block, handles);
}
self.std_scopes.push(StdScope::of(
block.stmts.iter().filter_map(|stmt| match stmt {
syn::Stmt::Item(item) => Some(item),
_ => None,
}),
false,
));
syn::visit_mut::visit_block_mut(self, block);
if self.compiles {
discard_call_bindings(&mut block.stmts);
}
self.std_scopes.pop();
}
fn visit_item_mod_mut(&mut self, node: &mut syn::ItemMod) {
let Some((_, items)) = &node.content else {
return;
};
self.std_scopes.push(StdScope::of(items.iter(), true));
syn::visit_mut::visit_item_mod_mut(self, node);
self.std_scopes.pop();
}
fn visit_arm_mut(&mut self, arm: &mut syn::Arm) {
if has_macro_attribute(&arm.attrs) {
self.observed().visit_arm_mut(arm);
return;
}
strip_inert_attrs(&mut arm.attrs);
arm.comma = None;
syn::visit_mut::visit_arm_mut(self, arm);
if self.compiles {
drop_binding_mut(&mut arm.pat);
}
}
fn visit_local_mut(&mut self, local: &mut syn::Local) {
if has_macro_attribute(&local.attrs) {
self.observed().visit_local_mut(local);
return;
}
syn::visit_mut::visit_local_mut(self, local);
if self.compiles {
drop_binding_mut(&mut local.pat);
}
}
fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
if has_macro_attribute(expr_attrs(expr)) {
self.observed().visit_expr_mut(expr);
return;
}
syn::visit_mut::visit_expr_mut(self, expr);
fold_paren_expr(expr);
if let syn::Expr::If(expr_if) = expr {
collapse_else_if(expr_if);
if self.compiles {
fold_empty_else(expr_if);
}
}
if self.compiles
&& let syn::Expr::Match(expr_match) = expr
{
join_equal_arms(expr_match, &|_| false);
}
if let syn::Expr::Closure(closure) = expr {
let protected_inputs = closure
.inputs
.iter_mut()
.any(|pat| pat_attrs(pat).is_some_and(|attrs| has_macro_attribute(attrs)));
if !protected_inputs {
if closure.inputs.trailing_punct() {
closure.inputs.pop_punct();
}
if matches!(
closure.body.as_ref(),
syn::Expr::Block(block)
if block.attrs.is_empty()
&& block.label.is_none()
&& block.block.stmts.is_empty()
) {
fold_unit_return(&mut closure.output);
}
}
}
if self.compiles {
if let syn::Expr::Return(ret) = expr
&& ret.expr.as_ref().is_some_and(
|value| matches!(value.as_ref(), syn::Expr::Tuple(tuple) if tuple.elems.is_empty() && tuple.attrs.is_empty()),
)
{
ret.expr = None;
}
match expr {
syn::Expr::Closure(closure) => {
closure.inputs.iter_mut().for_each(drop_binding_mut);
if closure.asyncness.is_none()
&& let syn::Expr::Block(body) = closure.body.as_mut()
&& body.attrs.is_empty()
&& body.label.is_none()
{
guard_clauses(&mut body.block.stmts, &returns);
}
}
syn::Expr::ForLoop(for_loop) => {
drop_binding_mut(&mut for_loop.pat);
let label = for_loop.label.as_ref().map(|label| &label.name);
guard_clauses(&mut for_loop.body.stmts, &|stmt| continues(stmt, label));
}
syn::Expr::While(while_loop) => {
let label = while_loop.label.as_ref().map(|label| &label.name);
guard_clauses(&mut while_loop.body.stmts, &|stmt| continues(stmt, label));
}
syn::Expr::Loop(plain) => {
let label = plain.label.as_ref().map(|label| &label.name);
guard_clauses(&mut plain.body.stmts, &|stmt| continues(stmt, label));
}
syn::Expr::Let(expr_let) => drop_binding_mut(&mut expr_let.pat),
_ => {}
}
loop_as_while(expr);
}
fold_signed_zero(expr);
if self.compiles {
fold_scalar_const(expr);
}
}
fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
if let Some(attrs) = pat_attrs(pat) {
if has_macro_attribute(attrs) {
self.observed().visit_pat_mut(pat);
return;
}
strip_inert_attrs(attrs);
}
syn::visit_mut::visit_pat_mut(self, pat);
fold_paren_pat(pat);
if self.compiles {
fold_pattern_spelling(pat);
}
}
fn visit_fn_arg_mut(&mut self, node: &mut syn::FnArg) {
let attrs = match node {
syn::FnArg::Receiver(receiver) => &mut receiver.attrs,
syn::FnArg::Typed(typed) => &mut typed.attrs,
};
if has_macro_attribute(attrs) {
self.observed().visit_fn_arg_mut(node);
return;
}
strip_inert_attrs(attrs);
syn::visit_mut::visit_fn_arg_mut(self, node);
if self.compiles {
match node {
syn::FnArg::Receiver(receiver) => receiver.mutability = None,
syn::FnArg::Typed(typed) => drop_binding_mut(&mut typed.pat),
}
}
if let syn::FnArg::Receiver(receiver) = node {
fold_explicit_receiver(receiver);
}
}
fn visit_named_arg_mut(&mut self, node: &mut syn::NamedArg) {
if has_macro_attribute(&node.attrs) {
self.observed().visit_named_arg_mut(node);
return;
}
strip_inert_attrs(&mut node.attrs);
syn::visit_mut::visit_named_arg_mut(self, node);
}
fn visit_type_mut(&mut self, ty: &mut syn::Type) {
if has_macro_attribute(type_attrs(ty)) {
self.observed().visit_type_mut(ty);
return;
}
syn::visit_mut::visit_type_mut(self, ty);
fold_paren_type(ty);
}
fn visit_type_path_mut(&mut self, node: &mut syn::TypePath) {
syn::visit_mut::visit_type_path_mut(self, node);
fold_type_path(&mut node.path);
}
fn visit_type_fn_ptr_mut(&mut self, node: &mut syn::TypeFnPtr) {
syn::visit_mut::visit_type_fn_ptr_mut(self, node);
if self.compiles {
fold_fn_pointer_lifetime(node);
}
fold_unit_return(&mut node.output);
}
fn visit_abi_mut(&mut self, node: &mut syn::Abi) {
if self.compiles && node.name.is_none() {
node.name = Some(syn::LitStr::new("C", node.extern_token.span));
}
}
fn visit_vis_restricted_mut(&mut self, node: &mut syn::VisRestricted) {
syn::visit_mut::visit_vis_restricted_mut(self, node);
if ["crate", "self", "super"]
.iter()
.any(|name| node.path.is_ident(name))
{
node.in_token = None;
}
}
fn visit_parenthesized_generic_arguments_mut(
&mut self,
node: &mut syn::ParenthesizedGenericArguments,
) {
syn::visit_mut::visit_parenthesized_generic_arguments_mut(self, node);
fold_unit_return(&mut node.output);
}
fn visit_expr_method_call_mut(&mut self, node: &mut syn::ExprMethodCall) {
syn::visit_mut::visit_expr_method_call_mut(self, node);
if node
.turbofish
.as_ref()
.is_some_and(|args| args.args.is_empty())
{
node.turbofish = None;
}
}
fn visit_attribute_mut(&mut self, _node: &mut syn::Attribute) {}
fn visit_angle_bracketed_generic_arguments_mut(
&mut self,
node: &mut syn::AngleBracketedGenericArguments,
) {
syn::visit_mut::visit_angle_bracketed_generic_arguments_mut(self, node);
node.args.pop_punct();
if self.compiles {
for arg in &mut node.args {
if let syn::GenericArgument::Const(expr) = arg {
fold_scalar_argument_block(expr);
}
}
}
}
fn visit_generics_mut(&mut self, generics: &mut syn::Generics) {
let observed = generics.params.iter().any(|param| {
has_macro_attribute(match param {
syn::GenericParam::Lifetime(param) => ¶m.attrs,
syn::GenericParam::Type(param) => ¶m.attrs,
syn::GenericParam::Const(param) => ¶m.attrs,
})
});
if observed {
self.observed().visit_generics_mut(generics);
return;
}
for param in &generics.params {
self.generic_names.push(match param {
syn::GenericParam::Type(param) => (false, param.ident.to_string()),
syn::GenericParam::Const(param) => (false, param.ident.to_string()),
syn::GenericParam::Lifetime(param) => (true, param.lifetime.ident.to_string()),
});
}
syn::visit_mut::visit_generics_mut(self, generics);
bounds_to_where(generics);
generics.params.pop_punct();
if let Some(clause) = &mut generics.where_clause {
for predicate in &mut clause.predicates {
match predicate {
syn::WherePredicate::Type(predicate) => {
drop_trailing_plus(&mut predicate.bounds);
}
syn::WherePredicate::Lifetime(predicate) => {
drop_trailing_plus(&mut predicate.bounds);
}
_ => {}
}
}
}
if self.compiles {
sort_bounds(generics, &self.generic_names);
}
}
fn visit_item_trait_mut(&mut self, node: &mut syn::ItemTrait) {
syn::visit_mut::visit_item_trait_mut(self, node);
drop_trailing_plus(&mut node.supertraits);
self.sort_bound_list(&mut node.supertraits);
}
fn visit_item_trait_alias_mut(&mut self, node: &mut syn::ItemTraitAlias) {
syn::visit_mut::visit_item_trait_alias_mut(self, node);
drop_trailing_plus(&mut node.bounds);
}
fn visit_trait_bound_mut(&mut self, node: &mut syn::TraitBound) {
syn::visit_mut::visit_trait_bound_mut(self, node);
fold_type_path(&mut node.path);
if node.paren_token.is_some() && !fn_sugar_output(&node.path) {
node.paren_token = None;
}
}
fn visit_trait_item_type_mut(&mut self, node: &mut syn::TraitItemType) {
syn::visit_mut::visit_trait_item_type_mut(self, node);
drop_trailing_plus(&mut node.bounds);
self.sort_bound_list(&mut node.bounds);
}
fn visit_type_impl_trait_mut(&mut self, node: &mut syn::TypeImplTrait) {
syn::visit_mut::visit_type_impl_trait_mut(self, node);
drop_trailing_plus(&mut node.bounds);
self.sort_bound_list(&mut node.bounds);
}
fn visit_type_trait_object_mut(&mut self, node: &mut syn::TypeTraitObject) {
syn::visit_mut::visit_type_trait_object_mut(self, node);
drop_trailing_plus(&mut node.bounds);
self.sort_bound_list(&mut node.bounds);
}
fn visit_constraint_mut(&mut self, node: &mut syn::Constraint) {
syn::visit_mut::visit_constraint_mut(self, node);
drop_trailing_plus(&mut node.bounds);
self.sort_bound_list(&mut node.bounds);
}
fn visit_type_reference_mut(&mut self, node: &mut syn::TypeReference) {
syn::visit_mut::visit_type_reference_mut(self, node);
if node.lifetime.as_ref().is_some_and(|l| l.ident == "_") {
node.lifetime = None;
}
}
fn visit_path_arguments_mut(&mut self, node: &mut syn::PathArguments) {
syn::visit_mut::visit_path_arguments_mut(self, node);
if !self.in_impl_header {
drop_anonymous_lifetimes(node);
}
if matches!(node, syn::PathArguments::AngleBracketed(args) if args.args.is_empty()) {
*node = syn::PathArguments::None;
}
}
fn visit_item_impl_mut(&mut self, node: &mut syn::ItemImpl) {
for attr in &mut node.attrs {
self.visit_attribute_mut(attr);
}
if self.compiles {
elide_impl_lifetimes(node);
}
self.visit_generics_mut(&mut node.generics);
let outer = core::mem::replace(&mut self.in_impl_header, true);
if let Some((path, _)) = &mut node.trait_ {
self.visit_path_mut(path);
fold_type_path(path);
}
self.visit_type_mut(&mut node.self_ty);
self.in_impl_header = outer;
for item in &mut node.items {
self.visit_impl_item_mut(item);
}
}
fn visit_item_const_mut(&mut self, node: &mut syn::ItemConst) {
syn::visit_mut::visit_item_const_mut(self, node);
StaticRefs.visit_type_mut(&mut node.ty);
}
fn visit_item_static_mut(&mut self, node: &mut syn::ItemStatic) {
syn::visit_mut::visit_item_static_mut(self, node);
StaticRefs.visit_type_mut(&mut node.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_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
syn::visit_mut::visit_item_fn_mut(self, node);
if self.compiles {
elide_lifetimes(&mut node.sig, Some(&node.block));
if node.sig.asyncness.is_none() {
guard_clauses(&mut node.block.stmts, &returns);
}
}
}
fn visit_impl_item_fn_mut(&mut self, node: &mut syn::ImplItemFn) {
syn::visit_mut::visit_impl_item_fn_mut(self, node);
if self.compiles {
elide_lifetimes(&mut node.sig, Some(&node.block));
if node.sig.asyncness.is_none() {
guard_clauses(&mut node.block.stmts, &returns);
}
}
}
fn visit_trait_item_fn_mut(&mut self, node: &mut syn::TraitItemFn) {
syn::visit_mut::visit_trait_item_fn_mut(self, node);
if self.compiles {
elide_lifetimes(&mut node.sig, node.default.as_ref());
if node.sig.asyncness.is_none()
&& let Some(body) = &mut node.default
{
guard_clauses(&mut body.stmts, &returns);
}
}
}
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 item_attrs(item).is_some_and(|attrs| has_macro_attribute(attrs)) {
self.observed().visit_item_mut(item);
return;
}
if let syn::Item::Struct(syn::ItemStruct { attrs, .. })
| syn::Item::Enum(syn::ItemEnum { attrs, .. })
| syn::Item::Union(syn::ItemUnion { attrs, .. }) = item
{
self.mark_rebound_derives(attrs);
if has_macro_derive(attrs) {
self.observed().visit_item_mut(item);
return;
}
fold_std_derives(attrs);
}
if let Some(attrs) = item_attrs(item) {
strip_inert_attrs(attrs);
}
let depth = self.generic_names.len();
syn::visit_mut::visit_item_mut(self, item);
self.generic_names.truncate(depth);
}
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
let attrs = match item {
syn::ImplItem::Const(v) => &mut v.attrs,
syn::ImplItem::Fn(v) => &mut v.attrs,
syn::ImplItem::Type(v) => &mut v.attrs,
syn::ImplItem::Macro(v) => &mut v.attrs,
_ => return,
};
if has_macro_attribute(attrs) {
self.observed().visit_impl_item_mut(item);
return;
}
strip_inert_attrs(attrs);
let depth = self.generic_names.len();
syn::visit_mut::visit_impl_item_mut(self, item);
self.generic_names.truncate(depth);
}
fn visit_trait_item_mut(&mut self, item: &mut syn::TraitItem) {
let attrs = match item {
syn::TraitItem::Const(v) => &mut v.attrs,
syn::TraitItem::Fn(v) => &mut v.attrs,
syn::TraitItem::Type(v) => &mut v.attrs,
syn::TraitItem::Macro(v) => &mut v.attrs,
_ => return,
};
if has_macro_attribute(attrs) {
self.observed().visit_trait_item_mut(item);
return;
}
strip_inert_attrs(attrs);
let depth = self.generic_names.len();
syn::visit_mut::visit_trait_item_mut(self, item);
self.generic_names.truncate(depth);
}
fn visit_foreign_item_mut(&mut self, item: &mut syn::ForeignItem) {
let attrs = match item {
syn::ForeignItem::Fn(v) => &v.attrs,
syn::ForeignItem::Static(v) => &v.attrs,
syn::ForeignItem::Type(v) => &v.attrs,
syn::ForeignItem::Macro(v) => &v.attrs,
_ => return,
};
if has_macro_attribute(attrs) {
self.observed().visit_foreign_item_mut(item);
return;
}
let depth = self.generic_names.len();
syn::visit_mut::visit_foreign_item_mut(self, item);
self.generic_names.truncate(depth);
}
fn visit_field_mut(&mut self, field: &mut syn::Field) {
if has_macro_attribute(&field.attrs) {
self.observed().visit_field_mut(field);
return;
}
strip_inert_attrs(&mut field.attrs);
syn::visit_mut::visit_field_mut(self, field);
}
fn visit_variant_mut(&mut self, variant: &mut syn::Variant) {
if has_macro_attribute(&variant.attrs) {
self.observed().visit_variant_mut(variant);
return;
}
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);
inline_format_args(&mut node.mac, self.rebound_names(), self.compiles);
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);
inline_format_args(&mut node.mac, self.rebound_names(), self.compiles);
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);
}
}
struct ObservedUses<'a> {
helpers: Option<&'a mut crate::helpers::HelperSyntax>,
}
impl VisitMut for ObservedUses<'_> {
#[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) {
let handles = self
.helpers
.as_mut()
.map(|helpers| helpers.take_block(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)),
);
if let Some(handles) = handles {
self.helpers
.as_mut()
.expect("tracked helper syntax")
.put_block(block, handles);
}
syn::visit_mut::visit_block_mut(self, block);
}
fn visit_attribute_mut(&mut self, _node: &mut syn::Attribute) {}
}
impl Drift<'_> {
fn within(frames: &[crate::scope::Frame]) -> Self {
Drift {
std_scopes: Vec::from([StdScope {
module: true,
rebound: frames_rebound(frames),
supers: 0,
}]),
..Drift::default()
}
}
fn sort_bound_list(
&self,
bounds: &mut syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>,
) {
if self.compiles {
sort_bound_slice(bounds, &self.generic_names);
}
}
fn observed(&mut self) -> ObservedUses<'_> {
ObservedUses {
helpers: self.helpers.as_deref_mut(),
}
}
fn mark_rebound_derives(&self, attrs: &mut [syn::Attribute]) {
use syn::parse::Parser;
let rebound = self.rebound_names();
if rebound == 0 {
return;
}
let parser = syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated;
let lists = attrs.iter_mut().filter_map(|attr| match &mut attr.meta {
syn::Meta::List(list) if list.path.is_ident("derive") => {
let paths = parser.parse2(list.tokens.clone()).ok()?;
Some((list, paths))
}
_ => None,
});
for (list, mut paths) in lists {
for path in &mut paths {
if path.leading_colon.is_none()
&& path.segments.len() == 1
&& let Some(index) = STD_DERIVES
.iter()
.position(|std| path.segments[0].ident == std)
&& rebound & (1 << index) != 0
{
let ident = &mut path.segments[0].ident;
*ident = syn::Ident::new_raw(STD_DERIVES[index], ident.span());
}
}
list.tokens = paths.into_token_stream();
}
}
fn rebound_names(&self) -> u64 {
let mut rebound = self.file_rebound;
let mut levels = 0;
for scope in self.std_scopes.iter().rev() {
rebound |= scope.rebound;
levels = levels.max(scope.supers);
if scope.module {
if levels == 0 {
return rebound;
}
levels -= 1;
}
}
StdScope::ALL
}
}
fn drop_binding_mut(pat: &mut syn::Pat) {
let pat = match pat {
syn::Pat::Type(typed) => typed.pat.as_mut(),
syn::Pat::Guard(guarded) => guarded.pat.as_mut(),
other => other,
};
if let syn::Pat::Ident(ident) = pat
&& ident.by_ref.is_none()
{
ident.mutability = None;
}
}
fn fold_explicit_receiver(receiver: &mut syn::Receiver) {
if matches!(
&receiver.kind,
syn::ReceiverKind::Typed(_, ty)
if matches!(ty.as_ref(), syn::Type::Path(path)
if path.attrs.is_empty() && path.qself.is_none() && path.path.is_ident("Self"))
) {
receiver.kind = syn::ReceiverKind::Value;
return;
}
if receiver.mutability.is_some() {
return;
}
let syn::ReceiverKind::Typed(_colon, ty) = &mut receiver.kind else {
return;
};
let syn::Type::Reference(reference) = &mut **ty else {
return;
};
if reference.mutability.is_some() {
return;
}
let syn::Type::Path(path) = reference.elem.as_ref() else {
return;
};
if path.attrs.is_empty() && path.qself.is_none() && path.path.is_ident("Self") {
let (ampersand, lifetime) = (
reference.and_token,
core::mem::take(&mut reference.lifetime),
);
receiver.kind = syn::ReceiverKind::Reference(ampersand, lifetime, None);
}
}
fn fold_pattern_spelling(pat: &mut syn::Pat) {
match pat {
syn::Pat::Or(pat) => pat.leading_vert = None,
syn::Pat::Ident(pat)
if pat.subpat.as_ref().is_some_and(
|(_, sub)| matches!(sub.as_ref(), syn::Pat::Wild(wild) if wild.attrs.is_empty()),
) =>
{
let name = pat.ident.to_string();
let name = name.strip_prefix("r#").unwrap_or(&name);
if !name.starts_with(char::is_uppercase) {
pat.subpat = None;
}
}
_ => {}
}
}
fn drop_block_statement_semicolons(stmts: &mut [syn::Stmt]) {
let Some((_, rest)) = stmts.split_last_mut() else {
return;
};
for stmt in rest {
if let syn::Stmt::Expr(expr, semi) = stmt
&& matches!(
expr,
syn::Expr::If(_)
| syn::Expr::Match(_)
| syn::Expr::Block(_)
| syn::Expr::Unsafe(_)
| syn::Expr::While(_)
| syn::Expr::Loop(_)
| syn::Expr::ForLoop(_)
| syn::Expr::TryBlock(_)
| syn::Expr::Const(_)
)
{
*semi = None;
}
}
}
fn collapse_else_if(expr_if: &mut syn::ExprIf) {
let Some((_, otherwise)) = &mut expr_if.else_branch else {
return;
};
let syn::Expr::Block(block) = otherwise.as_mut() else {
return;
};
if !matches!(block.block.stmts.as_slice(), [syn::Stmt::Expr(syn::Expr::If(inner), None)] if inner.attrs.is_empty())
{
return;
}
if let Some(syn::Stmt::Expr(inner, None)) = block.block.stmts.pop() {
**otherwise = inner;
}
}
fn guard_clauses(stmts: &mut Vec<syn::Stmt>, jump: &dyn Fn(&syn::Stmt) -> bool) {
let Some(index) = (0..stmts.len())
.find(|&index| guard(&mut stmts[index], jump).is_some() && settled(&stmts[index + 1..]))
else {
return;
};
let mut rest = stmts.split_off(index + 1);
guard_clauses(&mut rest, jump);
let (branch, semi) = stmts
.last_mut()
.and_then(|stmt| guard(stmt, jump))
.expect("the guard at the found index");
branch.then_branch.stmts.pop();
guard_clauses(&mut branch.then_branch.stmts, jump);
*semi = None;
if branch.then_branch.stmts.is_empty() {
negate_condition(&mut branch.cond);
branch.then_branch.stmts = rest;
return;
}
branch.else_branch = Some((
syn::token::Else::default(),
Box::new(syn::Expr::Block(syn::ExprBlock {
attrs: Vec::new(),
label: None,
block: syn::Block {
brace_token: syn::token::Brace::default(),
stmts: rest,
},
})),
));
collapse_else_if(branch);
}
fn negate_condition(cond: &mut syn::Expr) {
let inner = core::mem::replace(cond, syn::Expr::Verbatim(TokenStream::new()));
*cond = match inner {
syn::Expr::Unary(unary)
if unary.attrs.is_empty() && matches!(unary.op, syn::UnOp::Not(_)) =>
{
*unary.expr
}
inner => syn::Expr::Unary(syn::ExprUnary {
attrs: Vec::new(),
op: syn::UnOp::Not(syn::token::Not::default()),
expr: Box::new(inner),
}),
};
}
fn guard<'a>(
stmt: &'a mut syn::Stmt,
jump: &dyn Fn(&syn::Stmt) -> bool,
) -> Option<(&'a mut syn::ExprIf, &'a mut Option<syn::token::Semi>)> {
match stmt {
syn::Stmt::Expr(syn::Expr::If(branch), semi)
if branch.attrs.is_empty()
&& branch.else_branch.is_none()
&& !binds(&branch.cond)
&& branch.then_branch.stmts.last().is_some_and(jump) =>
{
Some((branch, semi))
}
_ => None,
}
}
fn binds(cond: &syn::Expr) -> bool {
match cond {
syn::Expr::Let(_) => true,
syn::Expr::Binary(binary) if matches!(binary.op, syn::BinOp::And(_)) => {
binds(&binary.left) || binds(&binary.right)
}
_ => false,
}
}
fn settled(rest: &[syn::Stmt]) -> bool {
rest.last().is_some_and(|last| match last {
syn::Stmt::Expr(_, semi) => semi.is_some(),
syn::Stmt::Macro(mac) => mac.semi_token.is_some(),
_ => true,
}) && !rest.iter().any(|stmt| matches!(stmt, syn::Stmt::Item(_)))
}
fn returns(stmt: &syn::Stmt) -> bool {
matches!(stmt, syn::Stmt::Expr(syn::Expr::Return(ret), _) if ret.attrs.is_empty() && ret.expr.is_none())
}
fn continues(stmt: &syn::Stmt, label: Option<&syn::Lifetime>) -> bool {
matches!(
stmt,
syn::Stmt::Expr(syn::Expr::Continue(jump), _)
if jump.attrs.is_empty()
&& jump.label.as_ref().is_none_or(|name| Some(name) == label)
)
}
fn loop_as_while(expr: &mut syn::Expr) {
let syn::Expr::Loop(plain) = expr else {
return;
};
let label = plain.label.as_ref().map(|label| &label.name);
let breaks = |stmt: &syn::Stmt| {
matches!(
stmt,
syn::Stmt::Expr(syn::Expr::Break(jump), _)
if jump.attrs.is_empty()
&& jump.expr.is_none()
&& jump.label.as_ref().is_none_or(|name| Some(name) == label)
)
};
let Some((first, rest)) = plain.body.stmts.split_first_mut() else {
return;
};
if rest.iter().any(|stmt| matches!(stmt, syn::Stmt::Item(_))) {
return;
}
let Some((branch, _)) =
guard(first, &breaks).filter(|(branch, _)| branch.then_branch.stmts.len() == 1)
else {
return;
};
let cond = core::mem::replace(
&mut branch.cond,
Box::new(syn::Expr::Verbatim(TokenStream::new())),
);
plain.body.stmts.remove(0);
*expr = syn::Expr::While(syn::ExprWhile {
attrs: core::mem::take(&mut plain.attrs),
label: plain.label.take(),
while_token: syn::token::While::default(),
cond: Box::new(syn::Expr::Unary(syn::ExprUnary {
attrs: Vec::new(),
op: syn::UnOp::Not(syn::token::Not::default()),
expr: cond,
})),
body: syn::Block {
brace_token: syn::token::Brace::default(),
stmts: core::mem::take(&mut plain.body.stmts),
},
});
}
pub(crate) fn join_equal_arms(
expr_match: &mut syn::ExprMatch,
unit: &dyn Fn(&syn::PatIdent) -> bool,
) {
use quote::ToTokens as _;
let arms = core::mem::take(&mut expr_match.arms);
let mut joined: Vec<syn::Arm> = Vec::with_capacity(arms.len());
let mut previous: Option<String> = None;
for arm in arms {
let body = (arm.attrs.is_empty() && binding_free(&arm.pat, unit))
.then(|| arm.body.to_token_stream().to_string());
if let Some(last) = joined.last_mut()
&& body.is_some()
&& body == previous
{
join_alternative(&mut last.pat, arm.pat);
continue;
}
previous = body;
joined.push(arm);
}
expr_match.arms = joined;
}
fn join_alternative(into: &mut syn::Pat, pat: syn::Pat) {
let first = core::mem::replace(into, syn::Pat::Verbatim(proc_macro2::TokenStream::new()));
let mut joined = match first {
syn::Pat::Or(alternatives) => alternatives,
single => syn::PatOr {
attrs: Vec::new(),
leading_vert: None,
cases: core::iter::once(single).collect(),
},
};
match pat {
syn::Pat::Or(alternatives) => joined.cases.extend(alternatives.cases),
single => joined.cases.push(single),
}
*into = syn::Pat::Or(joined);
}
fn binding_free(pat: &syn::Pat, unit: &dyn Fn(&syn::PatIdent) -> bool) -> bool {
let free = |pat: &syn::Pat| binding_free(pat, unit);
match pat {
syn::Pat::Lit(lit) => lit.attrs.is_empty(),
syn::Pat::Range(range) => range.attrs.is_empty(),
syn::Pat::Wild(wild) => wild.attrs.is_empty(),
syn::Pat::Rest(rest) => rest.attrs.is_empty(),
syn::Pat::Path(path) => path.attrs.is_empty(),
syn::Pat::Ident(ident) => unit(ident),
syn::Pat::Reference(reference) => reference.attrs.is_empty() && free(&reference.pat),
syn::Pat::Or(or) => or.attrs.is_empty() && or.cases.iter().all(free),
syn::Pat::Tuple(tuple) => tuple.attrs.is_empty() && tuple.elems.iter().all(free),
syn::Pat::Slice(slice) => slice.attrs.is_empty() && slice.elems.iter().all(free),
syn::Pat::TupleStruct(tuple) => tuple.attrs.is_empty() && tuple.elems.iter().all(free),
syn::Pat::Struct(pat) => {
pat.attrs.is_empty()
&& pat.rest.as_ref().is_none_or(|rest| rest.attrs.is_empty())
&& pat
.fields
.iter()
.all(|field| field.attrs.is_empty() && free(&field.pat))
}
_ => false,
}
}
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 discard_call_bindings(stmts: &mut [syn::Stmt]) {
for stmt in stmts {
if let syn::Stmt::Local(local) = stmt
&& local.attrs.is_empty()
&& matches!(&local.pat, syn::Pat::Wild(wild) if wild.attrs.is_empty())
&& let Some(init) = &mut local.init
&& init.diverge.is_none()
&& matches!(
init.expr.as_ref(),
syn::Expr::Call(_) | syn::Expr::MethodCall(_) | syn::Expr::Await(_)
)
&& expr_attrs(&init.expr).is_empty()
{
let semi = local.semi_token;
let expr = core::mem::replace(
init.expr.as_mut(),
syn::Expr::Verbatim(proc_macro2::TokenStream::new()),
);
*stmt = syn::Stmt::Expr(expr, Some(semi));
}
}
}
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.attrs.is_empty() && t.elems.is_empty())
{
*output = syn::ReturnType::Default;
}
}
fn fold_fn_pointer_lifetime(node: &mut syn::TypeFnPtr) {
use syn::visit::Visit;
let Some(bound) = &node.lifetimes else {
return;
};
let Some(syn::GenericParam::Lifetime(binder)) = bound.lifetimes.first() else {
return;
};
if bound.lifetimes.len() != 1
|| node.inputs.len() != 1
|| node.variadic.is_some()
|| !binder.attrs.is_empty()
|| !binder.bounds.is_empty()
|| !node.inputs[0].attrs.is_empty()
{
return;
}
let spelling = binder.lifetime.ident.to_string();
if spelling.starts_with("r#") || matches!(spelling.as_str(), "static" | "_") {
return;
}
let syn::Type::Reference(reference) = &mut node.inputs[0].ty else {
return;
};
if reference.lifetime.as_ref() != Some(&binder.lifetime) {
return;
}
let mut uses = LifetimeUses {
name: &binder.lifetime.ident,
count: 0,
opaque: false,
};
uses.visit_type_reference(reference);
uses.visit_return_type(&node.output);
if uses.opaque || uses.count != 1 {
return;
}
reference.lifetime = None;
node.lifetimes = None;
}
fn elide_lifetimes(sig: &mut syn::Signature, body: Option<&syn::Block>) {
let candidates: Vec<syn::Ident> = sig
.generics
.params
.iter()
.filter_map(|param| match param {
syn::GenericParam::Lifetime(param)
if param.attrs.is_empty() && param.bounds.is_empty() =>
{
Some(param.lifetime.ident.clone())
}
_ => None,
})
.collect();
for name in candidates {
if elidable(sig, body, &name) {
elide(sig, &name);
}
}
}
fn elidable(sig: &syn::Signature, body: Option<&syn::Block>, name: &syn::Ident) -> bool {
use syn::visit::Visit;
let mut bounds = LifetimeUses {
name,
count: 0,
opaque: false,
};
for param in &sig.generics.params {
if !matches!(param, syn::GenericParam::Lifetime(param) if param.lifetime.ident == *name) {
bounds.visit_generic_param(param);
}
}
if let Some(clause) = &sig.generics.where_clause {
bounds.visit_where_clause(clause);
}
if bounds.opaque || bounds.count != 0 {
return false;
}
let mut mentions = BodyMentions { name, found: false };
if let Some(body) = body {
mentions.visit_block(body);
}
if mentions.found {
return false;
}
let mut inputs = SignatureUses::new(name);
let mut reference_receiver = false;
let mut plain_receiver = true;
let mut in_receiver = false;
for input in &sig.inputs {
match input {
syn::FnArg::Receiver(receiver) => {
let known = matches!(
receiver.kind,
syn::ReceiverKind::Value
| syn::ReceiverKind::Reference(..)
| syn::ReceiverKind::Typed(..)
);
if !receiver.attrs.is_empty() || !known {
return false;
}
if let syn::ReceiverKind::Reference(_, lifetime, _) = &receiver.kind {
reference_receiver = true;
inputs.positions += 1;
if lifetime
.as_ref()
.is_some_and(|lifetime| lifetime.ident == *name)
{
inputs.references += 1;
in_receiver = true;
}
}
if let syn::ReceiverKind::Typed(_, ty) = &receiver.kind {
plain_receiver &=
matches!(ty.as_ref(), syn::Type::Path(path) if path.path.is_ident("Self"));
inputs.visit_type(ty);
}
}
syn::FnArg::Typed(typed) => {
if !typed.attrs.is_empty() {
return false;
}
inputs.visit_pat(&typed.pat);
inputs.visit_type(&typed.ty);
}
}
}
let mut outputs = SignatureUses::new(name);
outputs.visit_return_type(&sig.output);
if inputs.opaque || outputs.opaque || inputs.other || outputs.other || inputs.references != 1 {
return false;
}
outputs.references == 0
|| if reference_receiver {
in_receiver
} else {
plain_receiver && inputs.positions == 1
}
}
fn elide(sig: &mut syn::Signature, name: &syn::Ident) {
struct Elide<'a>(&'a syn::Ident);
impl VisitMut for Elide<'_> {
fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) {
if reference
.lifetime
.as_ref()
.is_some_and(|lifetime| lifetime.ident == *self.0)
{
reference.lifetime = None;
}
syn::visit_mut::visit_type_reference_mut(self, reference);
}
}
for input in &mut sig.inputs {
if let syn::FnArg::Receiver(receiver) = input
&& let syn::ReceiverKind::Reference(_, lifetime, _) = &mut receiver.kind
&& lifetime
.as_ref()
.is_some_and(|lifetime| lifetime.ident == *name)
{
*lifetime = None;
}
Elide(name).visit_fn_arg_mut(input);
}
Elide(name).visit_return_type_mut(&mut sig.output);
sig.generics.params = core::mem::take(&mut sig.generics.params)
.into_iter()
.filter(|param| {
!matches!(param, syn::GenericParam::Lifetime(param) if param.lifetime.ident == *name)
})
.collect();
if sig.generics.params.is_empty() {
sig.generics.lt_token = None;
sig.generics.gt_token = None;
}
}
fn elide_impl_lifetimes(node: &mut syn::ItemImpl) {
use syn::visit::Visit;
let candidates: Vec<syn::Ident> = node
.generics
.params
.iter()
.filter_map(|param| match param {
syn::GenericParam::Lifetime(param)
if param.attrs.is_empty() && param.bounds.is_empty() =>
{
Some(param.lifetime.ident.clone())
}
_ => None,
})
.collect();
for name in candidates {
let mut bounds = LifetimeUses {
name: &name,
count: 0,
opaque: false,
};
for param in &node.generics.params {
if !matches!(param, syn::GenericParam::Lifetime(param) if param.lifetime.ident == name)
{
bounds.visit_generic_param(param);
}
}
if let Some(clause) = &node.generics.where_clause {
bounds.visit_where_clause(clause);
}
let mut mentions = BodyMentions {
name: &name,
found: false,
};
for item in &node.items {
mentions.visit_impl_item(item);
}
let mut header = HeaderUses {
name: &name,
uses: 0,
other: false,
depth: 0,
};
if let Some((path, _)) = &node.trait_ {
header.visit_path(path);
}
header.visit_type(&node.self_ty);
if bounds.opaque || bounds.count != 0 || mentions.found || header.other || header.uses != 1
{
continue;
}
let mut anonymize = Anonymize(&name);
if let Some((path, _)) = &mut node.trait_ {
anonymize.visit_path_mut(path);
}
anonymize.visit_type_mut(&mut node.self_ty);
node.generics.params = core::mem::take(&mut node.generics.params)
.into_iter()
.filter(|param| {
!matches!(param, syn::GenericParam::Lifetime(param) if param.lifetime.ident == name)
})
.collect();
if node.generics.params.is_empty() {
node.generics.lt_token = None;
node.generics.gt_token = None;
}
}
}
struct Anonymize<'a>(&'a syn::Ident);
impl VisitMut for Anonymize<'_> {
fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) {
if lifetime.ident == *self.0 {
lifetime.ident = syn::Ident::new("_", lifetime.ident.span());
}
}
}
struct HeaderUses<'a> {
name: &'a syn::Ident,
uses: usize,
other: bool,
depth: usize,
}
impl HeaderUses<'_> {
fn count(&mut self, lifetime: &syn::Lifetime) {
if lifetime.ident == *self.name {
if self.depth == 0 {
self.uses += 1;
} else {
self.other = true;
}
}
}
}
impl<'ast> syn::visit::Visit<'ast> for HeaderUses<'_> {
fn visit_type_reference(&mut self, reference: &'ast syn::TypeReference) {
if let Some(lifetime) = &reference.lifetime {
self.count(lifetime);
}
self.visit_type(&reference.elem);
}
fn visit_generic_argument(&mut self, argument: &'ast syn::GenericArgument) {
match argument {
syn::GenericArgument::Lifetime(lifetime) => self.count(lifetime),
_ => syn::visit::visit_generic_argument(self, argument),
}
}
fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
self.other |= lifetime.ident == *self.name;
}
fn visit_type(&mut self, ty: &'ast syn::Type) {
match ty {
syn::Type::FnPtr(_) | syn::Type::TraitObject(_) | syn::Type::ImplTrait(_) => {
self.depth += 1;
syn::visit::visit_type(self, ty);
self.depth -= 1;
}
syn::Type::Macro(_) | syn::Type::Verbatim(_) => self.other = true,
_ => syn::visit::visit_type(self, ty),
}
}
fn visit_macro(&mut self, _: &'ast syn::Macro) {
self.other = true;
}
}
struct SignatureUses<'a> {
name: &'a syn::Ident,
references: usize,
positions: usize,
other: bool,
opaque: bool,
depth: usize,
}
impl<'a> SignatureUses<'a> {
fn new(name: &'a syn::Ident) -> Self {
Self {
name,
references: 0,
positions: 0,
other: false,
opaque: false,
depth: 0,
}
}
fn nested(&mut self, visit: impl FnOnce(&mut Self)) {
self.depth += 1;
visit(self);
self.depth -= 1;
}
}
impl<'ast> syn::visit::Visit<'ast> for SignatureUses<'_> {
fn visit_type_reference(&mut self, reference: &'ast syn::TypeReference) {
let named = reference
.lifetime
.as_ref()
.is_some_and(|lifetime| lifetime.ident == *self.name);
if self.depth == 0 {
self.positions += 1;
self.references += usize::from(named);
} else {
self.other |= named;
}
self.visit_type(&reference.elem);
}
fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
self.other |= lifetime.ident == *self.name;
self.positions += usize::from(self.depth == 0);
}
fn visit_type(&mut self, ty: &'ast syn::Type) {
match ty {
syn::Type::FnPtr(_) | syn::Type::TraitObject(_) | syn::Type::ImplTrait(_) => {
self.nested(|uses| syn::visit::visit_type(uses, ty));
}
syn::Type::Macro(_) | syn::Type::Verbatim(_) => self.opaque = true,
_ => syn::visit::visit_type(self, ty),
}
}
fn visit_parenthesized_generic_arguments(
&mut self,
arguments: &'ast syn::ParenthesizedGenericArguments,
) {
self.nested(|uses| syn::visit::visit_parenthesized_generic_arguments(uses, arguments));
}
fn visit_trait_bound(&mut self, bound: &'ast syn::TraitBound) {
self.nested(|uses| syn::visit::visit_trait_bound(uses, bound));
}
fn visit_macro(&mut self, _: &'ast syn::Macro) {
self.opaque = true;
}
}
struct BodyMentions<'a> {
name: &'a syn::Ident,
found: bool,
}
impl BodyMentions<'_> {
fn scan(&mut self, tokens: &TokenStream) {
let mut quote = false;
for token in tokens.clone() {
match token {
proc_macro2::TokenTree::Punct(punct) => quote = punct.as_char() == '\'',
proc_macro2::TokenTree::Ident(ident) => {
self.found |= quote && ident == *self.name;
quote = false;
}
proc_macro2::TokenTree::Group(group) => {
self.scan(&group.stream());
quote = false;
}
proc_macro2::TokenTree::Literal(_) => quote = false,
}
}
}
}
impl<'ast> syn::visit::Visit<'ast> for BodyMentions<'_> {
fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
self.found |= lifetime.ident == *self.name;
}
fn visit_macro(&mut self, mac: &'ast syn::Macro) {
self.scan(&mac.tokens);
}
fn visit_token_stream(&mut self, tokens: &'ast TokenStream) {
self.scan(tokens);
}
}
struct LifetimeUses<'a> {
name: &'a syn::Ident,
count: usize,
opaque: bool,
}
impl<'ast> syn::visit::Visit<'ast> for LifetimeUses<'_> {
fn visit_lifetime(&mut self, lifetime: &'ast syn::Lifetime) {
self.count += usize::from(lifetime.ident == *self.name);
}
fn visit_attribute(&mut self, _: &'ast syn::Attribute) {
self.opaque = true;
}
fn visit_bound_lifetimes(&mut self, _: &'ast syn::BoundLifetimes) {
self.opaque = true;
}
fn visit_type(&mut self, ty: &'ast syn::Type) {
if matches!(
ty,
syn::Type::FnPtr(_) | syn::Type::Group(_) | syn::Type::Verbatim(_)
) {
self.opaque = true;
}
syn::visit::visit_type(self, ty);
}
fn visit_macro(&mut self, _: &'ast syn::Macro) {
self.opaque = true;
}
fn visit_expr(&mut self, expr: &'ast syn::Expr) {
if matches!(expr, syn::Expr::Group(_) | syn::Expr::Verbatim(_)) {
self.opaque = true;
}
syn::visit::visit_expr(self, expr);
}
fn visit_type_param_bound(&mut self, bound: &'ast syn::TypeParamBound) {
if matches!(bound, syn::TypeParamBound::Verbatim(_)) {
self.opaque = true;
}
syn::visit::visit_type_param_bound(self, bound);
}
fn visit_token_stream(&mut self, _: &'ast TokenStream) {
self.opaque = true;
}
fn visit_lit(&mut self, literal: &'ast syn::Lit) {
if matches!(literal, syn::Lit::Verbatim(_)) {
self.opaque = true;
}
syn::visit::visit_lit(self, literal);
}
}
fn fold_empty_else(expr_if: &mut syn::ExprIf) {
if expr_if
.else_branch
.as_ref()
.is_some_and(|(_, alternative)| {
matches!(
alternative.as_ref(),
syn::Expr::Block(block)
if block.attrs.is_empty()
&& block.label.is_none()
&& block.block.stmts.is_empty()
)
})
{
expr_if.else_branch = None;
}
}
fn fold_scalar_const(expr: &mut syn::Expr) {
let syn::Expr::Const(block) = expr else {
return;
};
if block.attrs.is_empty()
&& scalar_tail(&block.block.stmts)
&& let Some(syn::Stmt::Expr(value, None)) = block.block.stmts.pop()
{
*expr = value;
}
}
pub(crate) fn fold_scalar_argument_block(expr: &mut syn::Expr) {
let syn::Expr::Block(block) = expr else {
return;
};
if block.attrs.is_empty()
&& block.label.is_none()
&& scalar_tail(&block.block.stmts)
&& let Some(syn::Stmt::Expr(value, None)) = block.block.stmts.pop()
{
*expr = value;
}
}
fn scalar_tail(stmts: &[syn::Stmt]) -> bool {
matches!(
stmts,
[syn::Stmt::Expr(syn::Expr::Lit(literal), None)]
if literal.attrs.is_empty()
&& matches!(
literal.lit,
syn::Lit::Int(_)
| syn::Lit::Float(_)
| syn::Lit::Bool(_)
| syn::Lit::Char(_)
| syn::Lit::Byte(_)
)
)
}
fn fold_signed_zero(expr: &mut syn::Expr) {
let syn::Expr::Unary(unary) = expr else {
return;
};
let syn::Expr::Lit(literal) = unary.expr.as_ref() else {
return;
};
if unary.attrs.is_empty()
&& literal.attrs.is_empty()
&& matches!(unary.op, syn::UnOp::Neg(_))
&& matches!(
&literal.lit,
syn::Lit::Int(integer)
if integer.base10_digits().bytes().all(|digit| digit == b'0')
&& ["i8", "i16", "i32", "i64", "i128", "isize"]
.contains(&integer.suffix())
)
{
*expr = core::mem::replace(unary.expr.as_mut(), syn::Expr::Verbatim(TokenStream::new()));
}
}
fn fold_type_path(path: &mut syn::Path) {
for segment in &mut path.segments {
if let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments {
args.colon2_token = None;
}
}
}
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 bounds_to_where(generics: &mut syn::Generics) {
let mut moved = Vec::new();
for param in &mut generics.params {
match param {
syn::GenericParam::Type(ty) if !ty.bounds.is_empty() => {
ty.colon_token = None;
moved.push(syn::WherePredicate::Type(syn::PredicateType {
attrs: Vec::new(),
lifetimes: None,
bounded_ty: syn::Type::Path(syn::TypePath {
attrs: Vec::new(),
qself: None,
path: ty.ident.clone().into(),
}),
colon_token: syn::token::Colon::default(),
bounds: core::mem::take(&mut ty.bounds),
}));
}
syn::GenericParam::Lifetime(lifetime) if !lifetime.bounds.is_empty() => {
lifetime.colon_token = None;
moved.push(syn::WherePredicate::Lifetime(syn::PredicateLifetime {
attrs: Vec::new(),
lifetime: lifetime.lifetime.clone(),
colon_token: syn::token::Colon::default(),
bounds: core::mem::take(&mut lifetime.bounds),
}));
}
_ => {}
}
}
if moved.is_empty() {
return;
}
let clause = generics.make_where_clause();
let written = core::mem::take(&mut clause.predicates);
clause.predicates.extend(moved);
clause.predicates.extend(written);
}
fn sort_bounds(generics: &mut syn::Generics, names: &[(bool, String)]) {
use quote::ToTokens as _;
let Some(clause) = &mut generics.where_clause else {
return;
};
let mut merged: Vec<(Option<String>, syn::WherePredicate)> =
Vec::with_capacity(clause.predicates.len());
for predicate in core::mem::take(&mut clause.predicates) {
let syn::WherePredicate::Type(mut next) = predicate else {
merged.push((None, predicate));
continue;
};
if !next.attrs.is_empty() || next.lifetimes.is_some() {
merged.push((None, syn::WherePredicate::Type(next)));
continue;
}
let key = next.bounded_ty.to_token_stream().to_string();
let earlier = merged
.iter_mut()
.find_map(|(earlier_key, earlier)| match earlier {
syn::WherePredicate::Type(earlier) if earlier_key.as_ref() == Some(&key) => {
Some(earlier)
}
_ => None,
});
if let Some(earlier) = earlier {
earlier.bounds.extend(core::mem::take(&mut next.bounds));
} else {
merged.push((Some(key), syn::WherePredicate::Type(next)));
}
}
for (key, predicate) in &mut merged {
if key.is_some()
&& let syn::WherePredicate::Type(predicate) = predicate
{
sort_bound_slice(&mut predicate.bounds, names);
}
}
clause
.predicates
.extend(merged.into_iter().map(|(_, predicate)| predicate));
}
fn sort_bound_slice(
bounds: &mut syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>,
names: &[(bool, String)],
) {
if bounds.len() < 2 || !bounds.iter().all(sortable_bound) {
return;
}
let mut sorted: Vec<syn::TypeParamBound> = core::mem::take(bounds).into_iter().collect();
sorted.sort_by_cached_key(|bound| bound_key(bound, names));
*bounds = sorted.into_iter().collect();
}
fn sortable_bound(bound: &syn::TypeParamBound) -> bool {
let free = |segment: &syn::PathSegment| match &segment.arguments {
syn::PathArguments::None => true,
syn::PathArguments::AngleBracketed(arguments) => arguments.args.iter().all(|argument| {
matches!(
argument,
syn::GenericArgument::Lifetime(_)
| syn::GenericArgument::Type(_)
| syn::GenericArgument::Const(_)
)
}),
syn::PathArguments::Parenthesized(_) => false,
};
matches!(bound, syn::TypeParamBound::Lifetime(_))
|| matches!(bound, syn::TypeParamBound::Trait(bound) if bound.path.segments.iter().all(free))
}
fn bound_key(bound: &syn::TypeParamBound, names: &[(bool, String)]) -> String {
use quote::ToTokens as _;
fn write(stream: TokenStream, names: &[(bool, String)], out: &mut String) {
let mut lifetime = false;
for token in stream {
match token {
proc_macro2::TokenTree::Group(group) => {
let _ = core::fmt::Write::write_fmt(
out,
format_args!("{:?}\u{2}", group.delimiter()),
);
write(group.stream(), names, out);
out.push('\u{3}');
lifetime = false;
}
proc_macro2::TokenTree::Ident(ident) => {
let text = ident.to_string();
match names
.iter()
.rposition(|(is_lifetime, name)| *is_lifetime == lifetime && *name == text)
{
Some(position) => {
out.push('#');
out.push_str(&position.to_string());
}
None => out.push_str(&text),
}
out.push(' ');
lifetime = false;
}
proc_macro2::TokenTree::Punct(punct) => {
out.push(punct.as_char());
lifetime = punct.as_char() == '\'';
}
proc_macro2::TokenTree::Literal(literal) => {
out.push_str(&literal.to_string());
out.push(' ');
lifetime = false;
}
}
}
}
let mut out = String::new();
write(bound.to_token_stream(), names, &mut out);
out
}
fn drop_trailing_plus<T>(bounds: &mut syn::punctuated::Punctuated<T, syn::Token![+]>) {
bounds.pop_punct();
}
fn fn_sugar_output(path: &syn::Path) -> bool {
matches!(
path.segments.last().map(|segment| &segment.arguments),
Some(syn::PathArguments::Parenthesized(args)) if matches!(args.output, syn::ReturnType::Type(..))
)
}
fn drop_anonymous_lifetimes(arguments: &mut syn::PathArguments) {
let syn::PathArguments::AngleBracketed(angle) = arguments else {
return;
};
let anonymous = |arg: &syn::GenericArgument| matches!(arg, syn::GenericArgument::Lifetime(l) if l.ident == "_");
if !angle.args.iter().any(anonymous) {
return;
}
angle.args = core::mem::take(&mut angle.args)
.into_iter()
.filter(|arg| !anonymous(arg))
.collect();
if angle.args.is_empty() {
*arguments = syn::PathArguments::None;
}
}
pub(crate) const STD_DERIVES: [&str; 9] = [
"Clone",
"Copy",
"Debug",
"Default",
"Eq",
"Hash",
"Ord",
"PartialEq",
"PartialOrd",
];
const FORMAT_MACROS: [(&str, usize, bool); 18] = [
("print", 0, false),
("println", 0, false),
("eprint", 0, false),
("eprintln", 0, false),
("format", 0, false),
("format_args", 0, false),
("write", 1, false),
("writeln", 1, false),
("panic", 0, true),
("unreachable", 0, true),
("todo", 0, true),
("unimplemented", 0, true),
("assert", 1, true),
("debug_assert", 1, true),
("assert_eq", 2, true),
("assert_ne", 2, true),
("debug_assert_eq", 2, true),
("debug_assert_ne", 2, true),
];
const CRATE_ROOTS: [&str; 3] = ["std", "core", "alloc"];
pub(crate) fn std_name_bit(name: &str) -> u64 {
STD_DERIVES
.into_iter()
.chain(crate::COMMA_BLIND_MACROS)
.chain(CRATE_ROOTS)
.position(|std| std == name)
.map_or(0, |index| 1 << index)
}
#[derive(Clone, Copy)]
struct StdScope {
module: bool,
rebound: u64,
supers: usize,
}
impl StdScope {
const ALL: u64 = ALL_STD_NAMES;
fn of<'a>(items: impl Iterator<Item = &'a syn::Item>, module: bool) -> Self {
let mut scope = Self {
module,
rebound: 0,
supers: 0,
};
for item in items {
scope.bind(item);
}
scope
}
fn bind(&mut self, item: &syn::Item) {
let bit = |ident: &syn::Ident| std_name_bit(&crate::scope::ident_name(ident));
let root = |ident: &syn::Ident| {
let name = crate::scope::ident_name(ident);
if CRATE_ROOTS.contains(&name.as_str()) {
std_name_bit(&name)
} else {
0
}
};
let bits = match item {
syn::Item::Use(item) => {
self.bind_tree(&item.tree, &mut Vec::new());
0
}
syn::Item::ExternCrate(krate) => krate
.rename
.as_ref()
.filter(|(_, rename)| *rename != krate.ident)
.map_or(0, |(_, rename)| root(rename)),
syn::Item::Macro(syn::ItemMacro {
ident: Some(ident), ..
}) => bit(ident),
_ => crate::alpha::type_item_ident(item).map_or(0, root),
};
self.rebound |= bits;
}
fn bind_tree<'a>(&mut self, tree: &'a syn::UseTree, prefix: &mut Vec<&'a syn::Ident>) {
let bit = |ident: &syn::Ident| std_name_bit(&crate::scope::ident_name(ident));
let from_std = matches!(glob_target(prefix), GlobTarget::Std);
match tree {
syn::UseTree::Path(path) => {
prefix.push(&path.ident);
self.bind_tree(&path.tree, prefix);
prefix.pop();
}
syn::UseTree::Name(_) if from_std => {}
syn::UseTree::Name(name) => self.rebound |= bit(&name.ident),
syn::UseTree::Rename(rename) if from_std && rename.ident == rename.rename => {}
syn::UseTree::Rename(rename) => self.rebound |= bit(&rename.rename),
syn::UseTree::Glob(_) => match glob_target(prefix) {
GlobTarget::Std => {}
GlobTarget::Ancestor(levels) => self.supers = self.supers.max(levels),
GlobTarget::Unknown => self.rebound = Self::ALL,
},
syn::UseTree::Group(group) => {
for tree in &group.items {
self.bind_tree(tree, prefix);
}
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum GlobTarget {
Std,
Ancestor(usize),
Unknown,
}
pub(crate) fn glob_target<S: ?Sized + PartialEq<str>>(prefix: &[&S]) -> GlobTarget {
let is = |segment: &&S, name: &str| **segment == *name;
match prefix {
[first, ..] if CRATE_ROOTS.iter().any(|root| is(first, root)) => GlobTarget::Std,
[only] if is(only, "self") => GlobTarget::Ancestor(0),
[_, ..] if prefix.iter().all(|segment| is(segment, "super")) => {
GlobTarget::Ancestor(prefix.len())
}
_ => GlobTarget::Unknown,
}
}
pub(crate) fn macro_glob<S: ?Sized + PartialEq<str>>(prefix: &[&S], depth: usize) -> bool {
match glob_target(prefix) {
GlobTarget::Std => false,
GlobTarget::Ancestor(levels) => levels > depth,
GlobTarget::Unknown => true,
}
}
pub(crate) const ALL_STD_NAMES: u64 =
(1 << (STD_DERIVES.len() + crate::COMMA_BLIND_MACROS.len() + CRATE_ROOTS.len())) - 1;
pub(crate) fn import_rebound<S: ?Sized + PartialEq<str>>(
prefix: &[&S],
name: &str,
alias: &str,
) -> u64 {
if matches!(glob_target(prefix), GlobTarget::Std) && name == alias {
0
} else {
std_name_bit(alias)
}
}
pub(crate) fn file_rebound(file: &syn::File) -> u64 {
struct Definitions(u64);
impl<'ast> syn::visit::Visit<'ast> for Definitions {
fn visit_item_macro(&mut self, item: &'ast syn::ItemMacro) {
if let Some(ident) = &item.ident {
self.0 |= std_name_bit(&crate::scope::ident_name(ident));
}
}
fn visit_item_extern_crate(&mut self, krate: &'ast syn::ItemExternCrate) {
if krate
.attrs
.iter()
.any(|attr| attr.path().is_ident("macro_use"))
{
self.0 = StdScope::ALL;
}
}
}
let mut definitions = Definitions(0);
syn::visit::visit_file(&mut definitions, file);
definitions.0
}
pub(crate) fn module_std_names(items: &[syn::Item]) -> (bool, usize) {
let scope = StdScope::of(items.iter(), true);
(scope.rebound == 0, scope.supers)
}
pub(crate) fn block_std_names(block: &syn::Block) -> bool {
let items = block.stmts.iter().filter_map(|stmt| match stmt {
syn::Stmt::Item(item) => Some(item),
_ => None,
});
StdScope::of(items, false).rebound == 0
}
pub(crate) fn has_macro_derive(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| macro_derive_meta(&attr.meta))
}
pub(crate) fn macro_derive_meta(meta: &syn::Meta) -> bool {
use syn::parse::Parser;
let parser = syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated;
meta.path().is_ident("derive")
&& meta.require_list().map_or(true, |list| {
parser.parse2(list.tokens.clone()).map_or(true, |paths| {
paths.iter().any(|path| {
path.get_ident()
.is_none_or(|name| !STD_DERIVES.iter().any(|std| name == std))
})
})
})
}
pub(crate) fn has_macro_attribute(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| macro_meta(&attr.meta))
}
pub(crate) fn macro_meta(meta: &syn::Meta) -> bool {
let path = meta.path();
if path.is_ident("cfg_attr") || path.is_ident("unsafe") {
let skip = usize::from(path.is_ident("cfg_attr"));
return crate::context::wrapped(meta, skip).is_none_or(|metas| {
metas
.iter()
.any(|meta| macro_meta(meta) || macro_derive_meta(meta))
});
}
let tool = path.segments.len() > 1
&& path.segments.first().is_some_and(|first| {
[
"clippy",
"diagnostic",
"miri",
"rust_analyzer",
"rustdoc",
"rustfmt",
]
.iter()
.any(|tool| first.ident == tool)
});
!tool
&& !inert_meta(meta)
&& ![
"cfg",
"derive",
"deny",
"forbid",
"inline",
"cold",
"repr",
"must_use",
"deprecated",
"non_exhaustive",
"track_caller",
"no_mangle",
"export_name",
"link_name",
"link_section",
"automatically_derived",
"macro_use",
"macro_export",
"path",
"no_std",
"no_main",
"no_implicit_prelude",
"feature",
"recursion_limit",
"type_length_limit",
"crate_name",
"crate_type",
"windows_subsystem",
]
.iter()
.any(|name| path.is_ident(name))
}
fn fold_std_derives(attrs: &mut Vec<syn::Attribute>) {
use syn::parse::Parser;
let mut names: Vec<syn::Ident> = Vec::new();
let mut first = None;
for (index, attr) in attrs.iter().enumerate() {
if !attr.path().is_ident("derive") {
continue;
}
let syn::Meta::List(list) = &attr.meta else {
return;
};
let parser = syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated;
let Ok(paths) = parser.parse2(list.tokens.clone()) else {
return;
};
for path in paths {
match path.get_ident() {
Some(name) if STD_DERIVES.contains(&name.to_string().as_str()) => {
names.push(name.clone());
}
_ => return,
}
}
first.get_or_insert(index);
}
let Some(first) = first else {
return;
};
names.sort_by_cached_key(ToString::to_string);
attrs.retain(|attr| !attr.path().is_ident("derive"));
attrs.insert(first, syn::parse_quote!(#[derive(#(#names),*)]));
}
struct StaticRefs;
impl VisitMut for StaticRefs {
fn visit_type_reference_mut(&mut self, node: &mut syn::TypeReference) {
if node.lifetime.as_ref().is_some_and(|l| l.ident == "static") {
node.lifetime = None;
}
syn::visit_mut::visit_type_reference_mut(self, node);
}
fn visit_type_fn_ptr_mut(&mut self, _: &mut syn::TypeFnPtr) {}
fn visit_parenthesized_generic_arguments_mut(
&mut self,
_: &mut syn::ParenthesizedGenericArguments,
) {
}
}
pub(crate) fn is_inert_attr(attr: &syn::Attribute) -> bool {
inert_meta(&attr.meta)
}
fn inert_meta(meta: &syn::Meta) -> bool {
let p = meta.path();
(p.is_ident("doc") && !computed_doc(meta))
|| p.is_ident("allow")
|| p.is_ident("expect")
|| p.is_ident("warn")
|| p.segments.first().is_some_and(|s| s.ident == "rustfmt")
}
fn computed_doc(meta: &syn::Meta) -> bool {
matches!(meta, syn::Meta::NameValue(nv) if matches!(nv.value, syn::Expr::Macro(_)))
}
pub(crate) fn strip_inert_attrs(attrs: &mut Vec<syn::Attribute>) {
attrs.retain(|attr| !is_inert_attr(attr));
}
pub(crate) fn pat_attrs(pat: &mut syn::Pat) -> Option<&mut Vec<syn::Attribute>> {
Some(match pat {
syn::Pat::Const(p) => &mut p.attrs,
syn::Pat::Guard(p) => &mut p.attrs,
syn::Pat::Ident(p) => &mut p.attrs,
syn::Pat::Lit(p) => &mut p.attrs,
syn::Pat::Macro(p) => &mut p.attrs,
syn::Pat::Or(p) => &mut p.attrs,
syn::Pat::Paren(p) => &mut p.attrs,
syn::Pat::Path(p) => &mut p.attrs,
syn::Pat::Range(p) => &mut p.attrs,
syn::Pat::Reference(p) => &mut p.attrs,
syn::Pat::Rest(p) => &mut p.attrs,
syn::Pat::Slice(p) => &mut p.attrs,
syn::Pat::Struct(p) => &mut p.attrs,
syn::Pat::Tuple(p) => &mut p.attrs,
syn::Pat::TupleStruct(p) => &mut p.attrs,
syn::Pat::Type(p) => &mut p.attrs,
syn::Pat::Wild(p) => &mut p.attrs,
_ => return None,
})
}
pub(crate) fn type_attrs(ty: &syn::Type) -> &[syn::Attribute] {
match ty {
syn::Type::Array(ty) => &ty.attrs,
syn::Type::FnPtr(ty) => &ty.attrs,
syn::Type::Group(ty) => &ty.attrs,
syn::Type::ImplTrait(ty) => &ty.attrs,
syn::Type::Macro(ty) => &ty.attrs,
syn::Type::Never(ty) => &ty.attrs,
syn::Type::Paren(ty) => &ty.attrs,
syn::Type::Path(ty) => &ty.attrs,
syn::Type::Ptr(ty) => &ty.attrs,
syn::Type::Reference(ty) => &ty.attrs,
syn::Type::Slice(ty) => &ty.attrs,
syn::Type::TraitObject(ty) => &ty.attrs,
syn::Type::Tuple(ty) => &ty.attrs,
_ => &[],
}
}
pub(crate) fn expr_attrs(expr: &syn::Expr) -> &[syn::Attribute] {
match expr {
syn::Expr::Array(expr) => &expr.attrs,
syn::Expr::Assign(expr) => &expr.attrs,
syn::Expr::Async(expr) => &expr.attrs,
syn::Expr::Await(expr) => &expr.attrs,
syn::Expr::Binary(expr) => &expr.attrs,
syn::Expr::Block(expr) => &expr.attrs,
syn::Expr::Break(expr) => &expr.attrs,
syn::Expr::Call(expr) => &expr.attrs,
syn::Expr::Cast(expr) => &expr.attrs,
syn::Expr::Closure(expr) => &expr.attrs,
syn::Expr::Const(expr) => &expr.attrs,
syn::Expr::Continue(expr) => &expr.attrs,
syn::Expr::Field(expr) => &expr.attrs,
syn::Expr::ForLoop(expr) => &expr.attrs,
syn::Expr::Group(expr) => &expr.attrs,
syn::Expr::If(expr) => &expr.attrs,
syn::Expr::Index(expr) => &expr.attrs,
syn::Expr::Let(expr) => &expr.attrs,
syn::Expr::Lit(expr) => &expr.attrs,
syn::Expr::Loop(expr) => &expr.attrs,
syn::Expr::Macro(expr) => &expr.attrs,
syn::Expr::Match(expr) => &expr.attrs,
syn::Expr::MethodCall(expr) => &expr.attrs,
syn::Expr::Paren(expr) => &expr.attrs,
syn::Expr::Path(expr) => &expr.attrs,
syn::Expr::Range(expr) => &expr.attrs,
syn::Expr::RawAddr(expr) => &expr.attrs,
syn::Expr::Reference(expr) => &expr.attrs,
syn::Expr::Repeat(expr) => &expr.attrs,
syn::Expr::Return(expr) => &expr.attrs,
syn::Expr::Struct(expr) => &expr.attrs,
syn::Expr::Try(expr) => &expr.attrs,
syn::Expr::TryBlock(expr) => &expr.attrs,
syn::Expr::Tuple(expr) => &expr.attrs,
syn::Expr::Unary(expr) => &expr.attrs,
syn::Expr::Unsafe(expr) => &expr.attrs,
syn::Expr::While(expr) => &expr.attrs,
syn::Expr::Yield(expr) => &expr.attrs,
_ => &[],
}
}
pub(crate) 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 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 mut stmt in region {
let hoistable = match &mut stmt {
syn::Stmt::Item(item) => {
item_attrs(item).is_none_or(|attrs| attrs.iter().all(is_inert_attr))
}
_ => 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 });
}
pub(crate) fn format_operand(path: &syn::Path) -> Option<(usize, bool)> {
let name = path.segments.last()?.ident.to_string();
FORMAT_MACROS
.iter()
.find(|(std, _, _)| *std == name)
.map(|&(_, at, panic)| (at, panic))
}
fn std_format_operand(path: &syn::Path, rebound: u64) -> Option<(usize, bool)> {
let operand = format_operand(path)?;
let free = |ident: &syn::Ident| rebound & std_name_bit(&crate::scope::ident_name(ident)) == 0;
let leaf = &path.segments.last()?.ident;
let plain = path
.segments
.iter()
.all(|segment| segment.arguments.is_none());
let proven = match path.segments.len() {
1 => path.leading_colon.is_none() && free(leaf),
2 => {
let root = &path.segments[0].ident;
(root == "std"
|| (root == "alloc" && leaf == "format")
|| (root == "core" && leaf == "format_args"))
&& (path.leading_colon.is_some() || free(root))
}
_ => false,
};
(plain && proven).then_some(operand)
}
fn inline_format_args(mac: &mut syn::Macro, rebound: u64, literals: bool) -> bool {
use syn::parse::Parser;
let Some((at, panic)) = std_format_operand(&mac.path, rebound) else {
return false;
};
let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
let Ok(mut args) = parser.parse2(mac.tokens.clone()) else {
return false;
};
let mut nested = NestedFormat {
rebound,
literals,
changed: false,
};
for arg in &mut args {
nested.visit_expr_mut(arg);
}
let inlined = !panic && inline_positional(at, &mut args, literals);
if nested.changed || inlined {
mac.tokens = args.to_token_stream();
}
nested.changed || inlined
}
struct NestedFormat {
rebound: u64,
literals: bool,
changed: bool,
}
impl VisitMut for NestedFormat {
fn visit_block_mut(&mut self, block: &mut syn::Block) {
let outer = self.rebound;
let scope = StdScope::of(
block.stmts.iter().filter_map(|stmt| match stmt {
syn::Stmt::Item(item) => Some(item),
_ => None,
}),
false,
);
self.rebound |= if scope.supers > 0 {
StdScope::ALL
} else {
scope.rebound
};
syn::visit_mut::visit_block_mut(self, block);
self.rebound = outer;
}
fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
self.changed |= inline_format_args(mac, self.rebound, self.literals);
}
}
enum Inline {
Name(String),
Text(String),
Kept,
}
fn inline_positional(
at: usize,
args: &mut syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
literals: bool,
) -> bool {
let positional: Vec<&syn::Expr> = args.iter().skip(at + 1).collect();
if positional
.iter()
.any(|arg| matches!(arg, syn::Expr::Assign(_)))
{
return false;
}
let inlines: Vec<Inline> = positional
.into_iter()
.map(|arg| inline_argument(arg, literals))
.collect();
let Some(syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(format),
..
})) = args.iter_mut().nth(at)
else {
return false;
};
let Some((text, moved)) = inline_placeholders(&format.value(), &inlines) else {
return false;
};
*format = syn::LitStr::new(&text, format.span());
let kept = core::mem::take(args)
.into_iter()
.enumerate()
.filter(|(index, _)| *index <= at || !moved[index - at - 1])
.map(|(_, arg)| arg);
args.extend(kept);
true
}
fn inline_argument(arg: &syn::Expr, literals: bool) -> Inline {
match arg {
syn::Expr::Lit(syn::ExprLit {
attrs,
lit: syn::Lit::Str(literal),
}) if literals && attrs.is_empty() && literal.suffix().is_empty() => {
Inline::Text(literal.value())
}
syn::Expr::Path(path) if path.attrs.is_empty() => path
.path
.get_ident()
.map(ToString::to_string)
.filter(|name| {
!name.starts_with("r#")
&& !matches!(name.as_str(), "self" | "Self" | "crate" | "super")
})
.map_or(Inline::Kept, Inline::Name),
_ => Inline::Kept,
}
}
fn inline_placeholders(format: &str, inlines: &[Inline]) -> Option<(String, Vec<bool>)> {
let mut out = String::with_capacity(format.len());
let mut moved = Vec::with_capacity(inlines.len());
let mut chars = format.chars().peekable();
while let Some(c) = chars.next() {
match c {
'{' | '}' if chars.peek() == Some(&c) => {
chars.next();
out.push(c);
out.push(c);
}
'{' => {
let mut body = String::new();
loop {
match chars.next()? {
'}' => break,
c => body.push(c),
}
}
let (arg, spec) = body
.split_once(':')
.map_or((body.as_str(), None), |(a, s)| (a, Some(s)));
let explicit_index = !arg.is_empty() && arg.bytes().all(|b| b.is_ascii_digit());
if explicit_index || spec.is_some_and(|spec| spec.contains(['$', '*'])) {
return None;
}
if arg.is_empty()
&& spec.is_none()
&& let Inline::Text(text) = inlines.get(moved.len())?
{
out.push_str(&text.replace('{', "{{").replace('}', "}}"));
moved.push(true);
continue;
}
out.push('{');
if arg.is_empty() {
let name = match inlines.get(moved.len())? {
Inline::Name(name) => Some(name.as_str()),
_ => None,
};
out.push_str(name.unwrap_or_default());
moved.push(name.is_some());
} else {
out.push_str(arg);
}
if let Some(spec) = spec {
out.push(':');
out.push_str(spec);
}
out.push('}');
}
'}' => return None,
c => out.push(c),
}
}
(moved.len() == inlines.len() && moved.contains(&true)).then_some((out, moved))
}