use super::fields::has_dyn_trait_object;
pub(crate) fn is_pub(vis: &syn::Visibility) -> bool {
matches!(vis, syn::Visibility::Public(_))
}
pub(crate) fn has_derive(attrs: &[syn::Attribute], derive_name: &str) -> bool {
for attr in attrs {
if attr.path().is_ident("derive") {
if let Ok(nested) =
attr.parse_args_with(syn::punctuated::Punctuated::<syn::Path, syn::token::Comma>::parse_terminated)
{
for path in &nested {
if path.is_ident(derive_name) || path.segments.last().is_some_and(|seg| seg.ident == derive_name) {
return true;
}
}
}
} else if attr.path().is_ident("cfg_attr") {
if cfg_attr_has_derive_name(attr, derive_name) {
return true;
}
}
}
false
}
fn cfg_attr_has_derive_name(attr: &syn::Attribute, derive_name: &str) -> bool {
cfg_attr_walk_derives(attr, |path| {
path.is_ident(derive_name) || path.segments.last().is_some_and(|seg| seg.ident == derive_name)
})
}
fn cfg_attr_has_derive_path(attr: &syn::Attribute, segments: &[&str]) -> bool {
cfg_attr_walk_derives(attr, |path| {
path.segments.len() == segments.len()
&& path
.segments
.iter()
.zip(segments.iter())
.all(|(seg, expected)| seg.ident == *expected)
})
}
fn cfg_attr_walk_derives(attr: &syn::Attribute, mut predicate: impl FnMut(&syn::Path) -> bool) -> bool {
let mut found = false;
let mut visit = |meta: &syn::Meta| {
if let syn::Meta::List(list) = meta {
if list.path.is_ident("derive") {
if let Ok(inner_paths) =
list.parse_args_with(syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated)
{
for path in &inner_paths {
if predicate(path) {
found = true;
}
}
}
}
}
};
cfg_attr_walk_inner_metas(attr, &mut visit);
found
}
fn cfg_attr_walk_inner_metas(attr: &syn::Attribute, visit: &mut impl FnMut(&syn::Meta)) {
let Ok(meta_list) = attr.meta.require_list() else {
return;
};
cfg_attr_meta_list_walk_inner_metas(meta_list, visit);
}
fn cfg_attr_meta_list_walk_inner_metas(meta_list: &syn::MetaList, visit: &mut impl FnMut(&syn::Meta)) {
use syn::Token;
use syn::parse::ParseStream;
let parse_fn = |input: ParseStream<'_>| -> syn::Result<()> {
let _condition: syn::Meta = input.parse()?;
let _: Token![,] = input.parse()?;
while !input.is_empty() {
let inner_meta: syn::Meta = input.parse()?;
match &inner_meta {
syn::Meta::List(inner_list) if inner_list.path.is_ident("cfg_attr") => {
cfg_attr_meta_list_walk_inner_metas(inner_list, visit);
}
_ => visit(&inner_meta),
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
}
Ok(())
};
let _ = syn::parse::Parser::parse2(parse_fn, meta_list.tokens.clone());
}
pub(crate) fn has_cfg_attribute(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|a| a.path().is_ident("cfg"))
}
pub(crate) fn extract_cfg_condition(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("cfg") {
if let Ok(tokens) = attr.meta.require_list() {
return Some(tokens.tokens.to_string());
}
}
}
None
}
pub(crate) fn is_test_gated(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
attr.path().is_ident("cfg")
&& attr
.parse_args::<syn::Meta>()
.map(|meta| cfg_meta_gates_on_test(&meta))
.unwrap_or(false)
})
}
fn cfg_meta_gates_on_test(meta: &syn::Meta) -> bool {
match meta {
syn::Meta::Path(path) => path.is_ident("test"),
syn::Meta::List(list) => {
if list.path.is_ident("not") {
return false;
}
if list.path.is_ident("all") || list.path.is_ident("any") {
let nested =
list.parse_args_with(syn::punctuated::Punctuated::<syn::Meta, syn::token::Comma>::parse_terminated);
if let Ok(nested) = nested {
return nested.iter().any(cfg_meta_gates_on_test);
}
}
false
}
syn::Meta::NameValue(_) => false,
}
}
pub(crate) fn extract_serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
let mut found: Option<String> = None;
for_each_serde_meta_list(attrs, |list| {
if found.is_none() {
found = serde_meta_list_lit_str(list, "rename_all");
}
});
found
}
fn for_each_serde_meta_list(attrs: &[syn::Attribute], mut visit: impl FnMut(&syn::MetaList)) {
for attr in attrs {
if attr.path().is_ident("serde") {
if let Ok(list) = attr.meta.require_list() {
visit(list);
}
} else if attr.path().is_ident("cfg_attr") {
cfg_attr_walk_inner_metas(attr, &mut |meta| {
if let syn::Meta::List(list) = meta {
if list.path.is_ident("serde") {
visit(list);
}
}
});
}
}
}
fn serde_meta_list_lit_str(list: &syn::MetaList, key: &str) -> Option<String> {
let mut found: Option<String> = None;
let _ = list.parse_nested_meta(|meta| {
if meta.path.is_ident(key) {
if let Ok(value) = meta.value() {
if let Ok(s) = value.parse::<syn::LitStr>() {
found = Some(s.value());
}
}
} else if let Ok(value) = meta.value() {
let _: syn::Expr = value.parse()?;
} else {
let _ = meta.parse_nested_meta(|_| Ok(()));
}
Ok(())
});
found
}
pub(crate) fn extract_binding_exclusion_reason(attrs: &[syn::Attribute]) -> Option<String> {
if has_doc_hidden(attrs) {
return Some("doc(hidden)".to_string());
}
if has_alef_skip(attrs) {
return Some("alef(skip)".to_string());
}
None
}
pub(crate) fn extract_field_binding_exclusion_reason(attrs: &[syn::Attribute], ty: &syn::Type) -> Option<String> {
if let Some(reason) = extract_binding_exclusion_reason(attrs) {
return Some(reason);
}
if has_dyn_trait_object(ty) {
return Some("dyn-trait-object".to_string());
}
None
}
fn has_doc_hidden(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if !attr.path().is_ident("doc") {
return false;
}
let Ok(list) = attr.meta.require_list() else {
return false;
};
list.parse_args::<syn::Ident>()
.map(|ident| ident == "hidden")
.unwrap_or(false)
})
}
fn has_alef_skip(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
let attr_str = quote::quote!(#attr).to_string();
let is_direct_alef = attr.path().is_ident("alef") && attr_str.contains("skip");
let is_cfg_attr_alef =
attr.path().is_ident("cfg_attr") && attr_str.contains("alef") && attr_str.contains("skip");
is_direct_alef || is_cfg_attr_alef
})
}
pub(crate) fn extract_serde_flatten(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
let attr_str = quote::quote!(#attr).to_string();
if !attr_str.contains("serde") {
return false;
}
attr_str.contains("flatten ,")
|| attr_str.contains("flatten,")
|| attr_str.contains("flatten )")
|| attr_str.contains("flatten)")
|| attr_str.ends_with("flatten")
})
}
pub(crate) fn extract_serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
attrs.iter().find_map(|attr| {
let attr_str = quote::quote!(#attr).to_string();
if !attr_str.contains("serde") || !attr_str.contains("rename") {
return None;
}
let needles = ["rename =", "rename="];
for needle in &needles {
if let Some(pos) = attr_str.find(needle) {
let before = &attr_str[..pos];
if before.ends_with("rename_all_") || before.ends_with("rename_all") {
continue;
}
let rest = &attr_str[pos + needle.len()..];
let after = rest.trim_start();
let start = after.find('"')?;
let value_start = &after[start + 1..];
let end = value_start.find('"')?;
return Some(value_start[..end].to_string());
}
}
None
})
}
pub(crate) fn extract_serde_default_path(attrs: &[syn::Attribute]) -> Option<String> {
attrs.iter().find_map(|attr| {
let attr_str = quote::quote!(#attr).to_string();
if !attr_str.contains("serde") {
return None;
}
let needles = ["default =", "default="];
for needle in &needles {
if let Some(pos) = attr_str.find(needle) {
let before = &attr_str[..pos];
if before.chars().last().is_some_and(|c| c.is_alphanumeric() || c == '_') {
continue;
}
let after = attr_str[pos + needle.len()..].trim_start();
let start = after.find('"')?;
let value_start = &after[start + 1..];
let end = value_start.find('"')?;
return Some(value_start[..end].to_string());
}
}
None
})
}
pub(crate) fn has_serde_default(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
let attr_str = quote::quote!(#attr).to_string();
if !attr_str.contains("serde") {
return false;
}
attr_str.contains("default =")
|| attr_str.contains("default ,")
|| attr_str.contains("default,")
|| attr_str.contains("default )")
|| attr_str.contains("default)")
|| attr_str.ends_with("default")
})
}
pub(crate) fn has_derive_path(attrs: &[syn::Attribute], segments: &[&str]) -> bool {
for attr in attrs {
if attr.path().is_ident("derive") {
if let Ok(nested) =
attr.parse_args_with(syn::punctuated::Punctuated::<syn::Path, syn::token::Comma>::parse_terminated)
{
for path in &nested {
if path.segments.len() == segments.len()
&& path
.segments
.iter()
.zip(segments.iter())
.all(|(seg, expected)| seg.ident == expected)
{
return true;
}
}
}
} else if attr.path().is_ident("cfg_attr") {
if cfg_attr_has_derive_path(attr, segments) {
return true;
}
}
}
false
}
pub(crate) fn is_thiserror_enum(attrs: &[syn::Attribute]) -> bool {
has_derive(attrs, "Error") || has_derive_path(attrs, &["thiserror", "Error"])
}
pub(crate) fn extract_error_message_template(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("error") {
if let Ok(lit) = attr.parse_args::<syn::LitStr>() {
return Some(lit.value());
}
}
}
None
}
pub(crate) fn has_field_attr(attrs: &[syn::Attribute], name: &str) -> bool {
attrs.iter().any(|a| a.path().is_ident(name))
}
pub(crate) fn extract_deprecation(attrs: &[syn::Attribute]) -> Option<crate::core::ir::DeprecationInfo> {
attrs.iter().find_map(|attr| {
if !attr.path().is_ident("deprecated") {
return None;
}
let mut info = crate::core::ir::DeprecationInfo::default();
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("since") {
if let Ok(v) = meta.value() {
if let Ok(s) = v.parse::<syn::LitStr>() {
let raw = s.value();
info.since = Some(raw.strip_prefix('v').map(str::to_owned).unwrap_or(raw));
}
}
} else if meta.path.is_ident("note") {
if let Ok(v) = meta.value() {
if let Ok(s) = v.parse::<syn::LitStr>() {
info.note = Some(s.value());
}
}
} else if let Ok(v) = meta.value() {
let _: syn::Expr = v.parse()?;
}
Ok(())
});
Some(info)
})
}
pub(crate) fn extract_alef_since(attrs: &[syn::Attribute]) -> Option<String> {
let raw = attrs.iter().find_map(|attr| {
if attr.path().is_ident("alef") {
let mut found = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("since") {
if let Ok(v) = meta.value() {
if let Ok(s) = v.parse::<syn::LitStr>() {
found = Some(s.value());
}
}
} else if let Ok(v) = meta.value() {
let _: syn::Expr = v.parse()?;
}
Ok(())
});
return found;
}
if attr.path().is_ident("cfg_attr") {
let mut found = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("alef") {
let _ = meta.parse_nested_meta(|inner| {
if inner.path.is_ident("since") {
if let Ok(v) = inner.value() {
if let Ok(s) = v.parse::<syn::LitStr>() {
found = Some(s.value());
}
}
} else if let Ok(v) = inner.value() {
let _: syn::Expr = v.parse()?;
}
Ok(())
});
} else if let Ok(v) = meta.value() {
let _: syn::Expr = v.parse()?;
} else {
let _ = meta.parse_nested_meta(|_| Ok(()));
}
Ok(())
});
return found;
}
None
})?;
Some(raw.strip_prefix('v').map(str::to_owned).unwrap_or(raw))
}
pub(crate) fn extract_version_annotation(attrs: &[syn::Attribute]) -> crate::core::ir::VersionAnnotation {
crate::core::ir::VersionAnnotation {
since: extract_alef_since(attrs),
deprecated: extract_deprecation(attrs),
}
}