use std::collections::{HashMap, HashSet};
use crate::resolve::strip_raw;
pub(crate) fn direct_path_value(attrs: &[syn::Attribute]) -> Option<String> {
attrs.iter().find_map(|attr| {
if !attr.path().is_ident("path") {
return None;
}
match &attr.meta {
syn::Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}),
..
}) => Some(s.value()),
_ => None,
}
})
}
pub(crate) fn has_cfg_attr(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| attr.path().is_ident("cfg"))
}
fn is_transparent_macro(item: &syn::ItemMacro) -> bool {
item.ident.is_none()
&& item
.mac
.path
.segments
.last()
.is_some_and(|seg| seg.ident == "cfg_if")
}
fn transparent_macro_arms(mac: &syn::Macro) -> Vec<Vec<syn::Item>> {
mac.parse_body_with(parse_transparent_arms)
.unwrap_or_default()
}
fn parse_transparent_arms(input: syn::parse::ParseStream) -> syn::Result<Vec<Vec<syn::Item>>> {
let mut arms = Vec::new();
while !input.is_empty() {
if input.peek(syn::token::Brace) {
let arm;
syn::braced!(arm in input);
match arm.parse::<syn::File>() {
Ok(file) => arms.push(file.items),
Err(_) => {
drain(&arm)?;
arms.push(Vec::new());
}
}
} else {
skip_token(input)?;
}
}
Ok(arms)
}
fn drain(input: syn::parse::ParseStream) -> syn::Result<()> {
while !input.is_empty() {
skip_token(input)?;
}
Ok(())
}
fn skip_token(input: syn::parse::ParseStream) -> syn::Result<()> {
input.step(|cursor| match cursor.token_tree() {
Some((_, rest)) => Ok(((), rest)),
None => Err(cursor.error("unexpected end of macro body")),
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct ArmKey {
invocation: u32,
arm: u32,
}
#[derive(Clone)]
pub(crate) struct FlatItem {
pub(crate) item: syn::Item,
pub(crate) in_transparent_arm: bool,
arm_key: Option<ArmKey>,
}
impl FlatItem {
pub(crate) fn plain(item: syn::Item) -> Self {
Self {
item,
in_transparent_arm: false,
arm_key: None,
}
}
fn in_arm(mut self, key: ArmKey) -> Self {
self.in_transparent_arm = true;
self.arm_key = self.arm_key.or(Some(key));
self
}
}
pub(crate) fn flatten_transparent_macros(items: &[syn::Item]) -> Vec<FlatItem> {
let mut next_invocation = 0u32;
flatten_transparent_macros_tagged(items, &mut next_invocation)
}
fn flatten_transparent_macros_tagged(
items: &[syn::Item],
next_invocation: &mut u32,
) -> Vec<FlatItem> {
let mut out = Vec::new();
for item in items {
match item {
syn::Item::Macro(mac) if is_transparent_macro(mac) => {
let invocation = *next_invocation;
*next_invocation += 1;
for (arm, arm_items) in transparent_macro_arms(&mac.mac).into_iter().enumerate() {
let key = ArmKey {
invocation,
arm: arm as u32,
};
out.extend(
flatten_transparent_macros_tagged(&arm_items, next_invocation)
.into_iter()
.map(|flat| flat.in_arm(key)),
);
}
}
other => out.push(FlatItem::plain(other.clone())),
}
}
out
}
pub(crate) fn flatten_transparent_macro_items(items: &[syn::Item]) -> Vec<syn::Item> {
flatten_transparent_macros(items)
.into_iter()
.map(|flat| flat.item)
.collect()
}
pub(crate) fn body_nested_impls(items: &[syn::Item]) -> Vec<syn::Item> {
let mut out = Vec::new();
for item in items {
let stmts: &[syn::Stmt] = match item {
syn::Item::Const(c) => match c.expr.as_ref() {
syn::Expr::Block(block) if block.label.is_none() => &block.block.stmts,
_ => continue,
},
syn::Item::Fn(f) => &f.block.stmts,
_ => continue,
};
out.extend(stmts.iter().filter_map(|stmt| match stmt {
syn::Stmt::Item(item @ syn::Item::Impl(_)) => Some(item.clone()),
_ => None,
}));
}
out
}
pub(crate) fn flatten_with_body_nested_impls(
items: &[syn::Item],
) -> (Vec<FlatItem>, Vec<syn::Item>) {
let flat = flatten_transparent_macros(items);
let plain: Vec<syn::Item> = flat.iter().map(|f| f.item.clone()).collect();
let nested_impls = body_nested_impls(&plain);
(flat, nested_impls)
}
pub(crate) fn child_module_decls(items: &[FlatItem]) -> Vec<(String, FlatItem)> {
items
.iter()
.filter_map(|flat| match &flat.item {
syn::Item::Mod(m) => Some((strip_raw(&m.ident.to_string()), flat.clone())),
_ => None,
})
.collect()
}
pub(crate) fn provably_mutually_exclusive(a: &FlatItem, b: &FlatItem) -> bool {
match (a.arm_key, b.arm_key) {
(Some(ka), Some(kb)) if ka.invocation == kb.invocation => ka.arm != kb.arm,
_ => bare_cfg_negates(item_own_attrs(&a.item), item_own_attrs(&b.item)),
}
}
fn shadowed_child_mod_names<'a>(
child_mods: &'a [(String, FlatItem)],
use_flat: &FlatItem,
) -> HashSet<&'a str> {
child_mods
.iter()
.filter(|(_, mod_flat)| !provably_mutually_exclusive(mod_flat, use_flat))
.map(|(name, _)| name.as_str())
.collect()
}
pub(crate) fn reexport_externs_for(
externs: &HashSet<String>,
child_mods: &[(String, FlatItem)],
use_flat: &FlatItem,
) -> HashSet<String> {
let shadowed = shadowed_child_mod_names(child_mods, use_flat);
externs
.iter()
.filter(|e| !shadowed.contains(e.as_str()))
.cloned()
.collect()
}
pub(crate) fn reexport_renames_for(
renames: &HashMap<String, String>,
child_mods: &[(String, FlatItem)],
use_flat: &FlatItem,
) -> HashMap<String, String> {
let shadowed = shadowed_child_mod_names(child_mods, use_flat);
renames
.iter()
.filter(|(alias, _)| !shadowed.contains(alias.as_str()))
.map(|(a, b)| (a.clone(), b.clone()))
.collect()
}
fn item_own_attrs(item: &syn::Item) -> &[syn::Attribute] {
match item {
syn::Item::Mod(m) => &m.attrs,
syn::Item::Use(u) => &u.attrs,
_ => &[],
}
}
fn bare_cfg_negates(attrs_a: &[syn::Attribute], attrs_b: &[syn::Attribute]) -> bool {
match (
sole_bare_cfg_predicate(attrs_a),
sole_bare_cfg_predicate(attrs_b),
) {
(Some(pa), Some(pb)) => meta_is_negation(&pa, &pb) || meta_is_negation(&pb, &pa),
_ => false,
}
}
fn sole_bare_cfg_predicate(attrs: &[syn::Attribute]) -> Option<syn::Meta> {
let cfg_attrs: Vec<&syn::Attribute> = attrs
.iter()
.filter(|attr| attr.path().is_ident("cfg"))
.collect();
match cfg_attrs.as_slice() {
[one] => one.parse_args::<syn::Meta>().ok(),
_ => None,
}
}
fn meta_is_negation(a: &syn::Meta, b: &syn::Meta) -> bool {
match b {
syn::Meta::List(list) if list.path.is_ident("not") => list
.parse_args::<syn::Meta>()
.is_ok_and(|inner| meta_eq(a, &inner)),
_ => false,
}
}
fn meta_eq(a: &syn::Meta, b: &syn::Meta) -> bool {
match (a, b) {
(syn::Meta::Path(pa), syn::Meta::Path(pb)) => meta_path_eq(pa, pb),
(syn::Meta::List(la), syn::Meta::List(lb)) => {
meta_path_eq(&la.path, &lb.path)
&& match (
la.parse_args_with(cfg_attr_metas),
lb.parse_args_with(cfg_attr_metas),
) {
(Ok(args_a), Ok(args_b)) => {
args_a.len() == args_b.len()
&& args_a.iter().zip(args_b.iter()).all(|(x, y)| meta_eq(x, y))
}
_ => false,
}
}
(syn::Meta::NameValue(nva), syn::Meta::NameValue(nvb)) => {
meta_path_eq(&nva.path, &nvb.path) && expr_str_lit_eq(&nva.value, &nvb.value)
}
_ => false,
}
}
fn meta_path_eq(a: &syn::Path, b: &syn::Path) -> bool {
a.segments.len() == b.segments.len()
&& a.segments
.iter()
.zip(b.segments.iter())
.all(|(x, y)| strip_raw(&x.ident.to_string()) == strip_raw(&y.ident.to_string()))
}
fn expr_str_lit_eq(a: &syn::Expr, b: &syn::Expr) -> bool {
match (a, b) {
(
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(sa),
..
}),
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(sb),
..
}),
) => sa.value() == sb.value(),
_ => false,
}
}
type MetaList = syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>;
fn cfg_attr_metas(input: syn::parse::ParseStream) -> syn::Result<MetaList> {
MetaList::parse_terminated(input)
}
pub(crate) fn cfg_attr_path_values(attrs: &[syn::Attribute]) -> Vec<String> {
attrs
.iter()
.filter(|attr| attr.path().is_ident("cfg_attr"))
.filter_map(|attr| {
attr.parse_args_with(cfg_attr_metas)
.ok()
.and_then(|metas| applied_metas_path_value(&metas))
})
.collect()
}
fn applied_metas_path_value(metas: &MetaList) -> Option<String> {
metas.iter().skip(1).find_map(meta_path_value)
}
fn meta_path_value(meta: &syn::Meta) -> Option<String> {
match meta {
syn::Meta::NameValue(syn::MetaNameValue {
path,
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}),
..
}) if path.is_ident("path") => Some(s.value()),
syn::Meta::List(list) if list.path.is_ident("cfg_attr") => list
.parse_args_with(cfg_attr_metas)
.ok()
.and_then(|metas| applied_metas_path_value(&metas)),
_ => None,
}
}
pub(crate) fn is_public(vis: &syn::Visibility) -> bool {
matches!(vis, syn::Visibility::Public(_))
}
pub(crate) fn visibility_rank(vis: &syn::Visibility) -> u8 {
match vis {
syn::Visibility::Public(_) => 3,
syn::Visibility::Restricted(r) => {
let single = if r.path.leading_colon.is_none() && r.path.segments.len() == 1 {
r.path.segments.first().map(|s| s.ident.to_string())
} else {
None
};
match single.as_deref() {
Some("crate") => 2,
Some("super") => 1,
Some("self") => 0,
_ => 2,
}
}
syn::Visibility::Inherited => 0,
}
}
fn vis_prefix(vis: &syn::Visibility) -> String {
match vis {
syn::Visibility::Public(_) => "pub".to_string(),
syn::Visibility::Restricted(r) => {
let path: Vec<String> = r
.path
.segments
.iter()
.map(|s| strip_raw(&s.ident.to_string()))
.collect();
let joined = path.join("::");
if r.in_token.is_some() && !matches!(joined.as_str(), "crate" | "super" | "self") {
format!("pub(in {joined})")
} else {
format!("pub({joined})")
}
}
syn::Visibility::Inherited => String::new(),
}
}
pub(crate) struct VisibleItem<'a> {
pub(crate) visibility: &'a syn::Visibility,
pub(crate) kind: VisibleItemKind,
pub(crate) name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum VisibleItemKind {
Fn,
Struct,
Enum,
Union,
Type,
Const,
Static,
Trait,
TraitAlias,
Mod,
ExternCrate,
Use,
}
impl VisibleItemKind {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Fn => "fn",
Self::Struct => "struct",
Self::Enum => "enum",
Self::Union => "union",
Self::Type => "type",
Self::Const => "const",
Self::Static => "static",
Self::Trait => "trait",
Self::TraitAlias => "trait_alias",
Self::Mod => "mod",
Self::ExternCrate => "extern_crate",
Self::Use => "use",
}
}
}
fn item_observation_parts(item: &syn::Item) -> Vec<VisibleItem<'_>> {
let observed = |visibility, kind, name| VisibleItem {
visibility,
kind,
name,
};
match item {
syn::Item::Fn(i) => vec![observed(
&i.vis,
VisibleItemKind::Fn,
i.sig.ident.to_string(),
)],
syn::Item::Struct(i) => vec![observed(
&i.vis,
VisibleItemKind::Struct,
i.ident.to_string(),
)],
syn::Item::Enum(i) => vec![observed(&i.vis, VisibleItemKind::Enum, i.ident.to_string())],
syn::Item::Union(i) => vec![observed(
&i.vis,
VisibleItemKind::Union,
i.ident.to_string(),
)],
syn::Item::Type(i) => vec![observed(&i.vis, VisibleItemKind::Type, i.ident.to_string())],
syn::Item::Const(i) => vec![observed(
&i.vis,
VisibleItemKind::Const,
i.ident.to_string(),
)],
syn::Item::Static(i) => vec![observed(
&i.vis,
VisibleItemKind::Static,
i.ident.to_string(),
)],
syn::Item::Trait(i) => vec![observed(
&i.vis,
VisibleItemKind::Trait,
i.ident.to_string(),
)],
syn::Item::TraitAlias(i) => vec![observed(
&i.vis,
VisibleItemKind::TraitAlias,
i.ident.to_string(),
)],
syn::Item::Mod(i) => vec![observed(&i.vis, VisibleItemKind::Mod, i.ident.to_string())],
syn::Item::ExternCrate(i) => vec![observed(
&i.vis,
VisibleItemKind::ExternCrate,
i.ident.to_string(),
)],
syn::Item::Use(i) => vec![observed(
&i.vis,
VisibleItemKind::Use,
format!(
"{}{}",
if i.leading_colon.is_some() { "::" } else { "" },
use_tree_desc(&i.tree)
),
)],
syn::Item::ForeignMod(item) => item
.items
.iter()
.filter_map(|foreign_item| match foreign_item {
syn::ForeignItem::Fn(f) => Some(observed(
&f.vis,
VisibleItemKind::Fn,
f.sig.ident.to_string(),
)),
syn::ForeignItem::Static(s) => Some(observed(
&s.vis,
VisibleItemKind::Static,
s.ident.to_string(),
)),
syn::ForeignItem::Type(t) => {
Some(observed(&t.vis, VisibleItemKind::Type, t.ident.to_string()))
}
_ => None,
})
.collect(),
_ => vec![],
}
}
pub(crate) fn item_observation(
item: &syn::Item,
ceiling_rank: u8,
) -> Vec<(String, VisibleItemKind, String)> {
item_observation_parts(item)
.into_iter()
.filter(|observed| visibility_rank(observed.visibility) > ceiling_rank)
.map(|observed| {
(
vis_prefix(observed.visibility),
observed.kind,
observed.name,
)
})
.collect()
}
fn use_tree_desc(tree: &syn::UseTree) -> String {
match tree {
syn::UseTree::Path(p) => {
format!(
"{}::{}",
strip_raw(&p.ident.to_string()),
use_tree_desc(&p.tree)
)
}
syn::UseTree::Name(n) => strip_raw(&n.ident.to_string()),
syn::UseTree::Rename(r) => format!(
"{} as {}",
strip_raw(&r.ident.to_string()),
strip_raw(&r.rename.to_string())
),
syn::UseTree::Glob(_) => "*".to_string(),
syn::UseTree::Group(g) => {
let inner: Vec<String> = g.items.iter().map(use_tree_desc).collect();
format!("{{{}}}", inner.join(", "))
}
}
}
pub(crate) enum GenericsPosition<'a> {
Bounds(&'a syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>),
Type(&'a syn::Type),
}
pub(crate) fn impl_generics_positions(
generics: &syn::Generics,
ordinal: usize,
) -> Vec<(String, Vec<GenericsPosition<'_>>)> {
let mut positions: Vec<(String, Vec<GenericsPosition<'_>>)> = Vec::new();
for param in &generics.params {
match param {
syn::GenericParam::Type(tp) => positions.push((
strip_raw(&tp.ident.to_string()),
vec![GenericsPosition::Bounds(&tp.bounds)],
)),
syn::GenericParam::Const(cp) => positions.push((
strip_raw(&cp.ident.to_string()),
vec![GenericsPosition::Type(&cp.ty)],
)),
syn::GenericParam::Lifetime(_) => {}
}
}
if let Some(where_clause) = &generics.where_clause {
for (bound_ordinal, predicate) in where_clause.predicates.iter().enumerate() {
if let syn::WherePredicate::Type(pt) = predicate {
let key = crate::resolve::type_to_string(&pt.bounded_ty)
.unwrap_or_else(|| format!("_#{ordinal}.{bound_ordinal}"));
positions.push((
key,
vec![
GenericsPosition::Type(&pt.bounded_ty),
GenericsPosition::Bounds(&pt.bounds),
],
));
}
}
}
positions
}