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 meta_list = match attr.meta.require_list() {
Ok(list) => list,
Err(_) => return false,
};
use syn::Token;
use syn::parse::ParseStream;
let mut found = false;
let parse_fn = |input: ParseStream<'_>| -> syn::Result<()> {
let _condition: syn::Meta = input.parse()?;
let _: Token![,] = input.parse()?;
while !input.is_empty() {
let attr_meta: syn::Meta = input.parse()?;
if let syn::Meta::List(list) = &attr_meta {
if list.path.is_ident("derive") {
let inner_paths =
list.parse_args_with(syn::punctuated::Punctuated::<syn::Path, Token![,]>::parse_terminated)?;
for path in &inner_paths {
if predicate(path) {
found = true;
}
}
}
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
}
}
Ok(())
};
let _ = syn::parse::Parser::parse2(parse_fn, meta_list.tokens.clone());
found
}
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 extract_serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
fn extract_from_serde(attr: &syn::Attribute) -> Option<String> {
let mut found: Option<String> = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("rename_all") {
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()?;
}
Ok(())
});
found
}
for attr in attrs {
if attr.path().is_ident("serde") {
if let Some(v) = extract_from_serde(attr) {
return Some(v);
}
} else if attr.path().is_ident("cfg_attr") {
let mut inner: Option<String> = None;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("serde") {
let _ = meta.parse_nested_meta(|inner_meta| {
if inner_meta.path.is_ident("rename_all") {
if let Ok(value) = inner_meta.value() {
if let Ok(s) = value.parse::<syn::LitStr>() {
inner = Some(s.value());
}
}
} else if let Ok(value) = inner_meta.value() {
let _: syn::Expr = value.parse()?;
}
Ok(())
});
} else if let Ok(value) = meta.value() {
let _: syn::Expr = value.parse()?;
}
Ok(())
});
if let Some(v) = inner {
return Some(v);
}
}
}
None
}
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 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),
}
}