use ahash::AHashMap;
use alef_core::ir::ApiSurface;
use alef_core::ir::{FunctionDef, MethodDef, ParamDef, ReceiverKind, TypeDef, TypeRef};
use syn;
use crate::type_resolver;
use super::defaults::extract_default_values;
use super::helpers::{
build_rust_path, extract_cfg_condition, extract_doc_comments, has_cfg_attribute, unwrap_optional,
};
pub(crate) fn extract_function(item: &syn::ItemFn, crate_name: &str, module_path: &str) -> Option<FunctionDef> {
if !item.sig.generics.params.is_empty() {
return None;
}
let cfg = extract_cfg_condition(&item.attrs);
let name = item.sig.ident.to_string();
let doc = extract_doc_comments(&item.attrs);
let mut is_async = item.sig.asyncness.is_some();
let (mut return_type, error_type, returns_ref) = resolve_return_type(&item.sig.output);
if !is_async {
if let Some(inner) = unwrap_future_return(&item.sig.output) {
is_async = true;
return_type = inner;
}
}
let params = extract_params(&item.sig.inputs);
let rust_path = build_rust_path(crate_name, module_path, &name);
Some(FunctionDef {
rust_path,
name,
params,
return_type,
is_async,
error_type,
doc,
cfg,
sanitized: false,
returns_ref,
return_newtype_wrapper: None,
})
}
pub(crate) fn extract_impl_block(
item: &syn::ItemImpl,
crate_name: &str,
module_path: &str,
surface: &mut ApiSurface,
type_index: &AHashMap<String, usize>,
) {
if item.trait_.is_some() {
extract_trait_impl_methods(item, crate_name, surface, type_index);
return;
}
let type_name = match &*item.self_ty {
syn::Type::Path(p) => p.path.segments.last().map(|s| s.ident.to_string()).unwrap_or_default(),
_ => return,
};
let methods: Vec<MethodDef> = item
.items
.iter()
.filter_map(|impl_item| {
if let syn::ImplItem::Fn(method) = impl_item {
if super::helpers::is_pub(&method.vis) {
if !method.sig.generics.params.is_empty() {
return None;
}
if has_cfg_attribute(&method.attrs) {
return None;
}
let method_name = method.sig.ident.to_string();
if method_name == "new" {
if let syn::ReturnType::Type(_, ty) = &method.sig.output {
if matches!(&**ty, syn::Type::Path(p) if p.path.is_ident("Self")) {
return None;
}
}
}
return Some(extract_method(method, crate_name, &type_name, None));
}
}
None
})
.collect();
if methods.is_empty() {
return;
}
if let Some(&idx) = type_index.get(&type_name) {
for method in methods {
if !surface.types[idx].methods.iter().any(|m| m.name == method.name) {
surface.types[idx].methods.push(method);
}
}
} else {
let rust_path = build_rust_path(crate_name, module_path, &type_name);
surface.types.push(TypeDef {
name: type_name.clone(),
rust_path,
fields: vec![],
methods,
is_opaque: true,
is_clone: false,
is_trait: false,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
doc: String::new(),
cfg: None,
serde_rename_all: None,
has_serde: false,
});
}
}
pub(crate) fn extract_trait_impl_methods(
item: &syn::ItemImpl,
crate_name: &str,
surface: &mut ApiSurface,
type_index: &AHashMap<String, usize>,
) {
let type_name = match &*item.self_ty {
syn::Type::Path(p) => p.path.segments.last().map(|s| s.ident.to_string()),
_ => None,
};
let Some(type_name) = type_name else { return };
let Some(&idx) = type_index.get(&type_name) else {
return;
};
const STD_TRAITS: &[&str] = &[
"Default",
"Clone",
"Copy",
"Debug",
"Display",
"Drop",
"PartialEq",
"Eq",
"PartialOrd",
"Ord",
"Hash",
"From",
"Into",
"TryFrom",
"TryInto",
"Iterator",
"IntoIterator",
"Send",
"Sync",
"Sized",
"Unpin",
"Serialize",
"Deserialize", ];
let trait_source = item.trait_.as_ref().and_then(|(_, path, _)| {
let segments: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
let trait_name = segments.last().map(|s| s.as_str()).unwrap_or("");
if STD_TRAITS.contains(&trait_name) {
return None;
}
if segments.len() == 1 {
let trait_name = &segments[0];
surface
.types
.iter()
.find(|t| t.is_trait && t.name == *trait_name)
.map(|t| t.rust_path.replace('-', "_"))
} else {
Some(segments.join("::").replace('-', "_"))
}
});
let type_def = &mut surface.types[idx];
if let Some((_, path, _)) = &item.trait_ {
if path.segments.last().is_some_and(|s| s.ident == "Default") {
type_def.has_default = true;
extract_default_values(item, &mut type_def.fields);
}
}
for impl_item in &item.items {
if let syn::ImplItem::Fn(method) = impl_item {
if !method.sig.generics.params.is_empty() {
continue;
}
if has_cfg_attribute(&method.attrs) {
continue;
}
let method_def = extract_method(method, crate_name, &type_name, trait_source.clone());
if !type_def.methods.iter().any(|m| m.name == method_def.name) {
type_def.methods.push(method_def);
}
}
}
}
pub(crate) fn extract_method(
method: &syn::ImplItemFn,
_crate_name: &str,
parent_type_name: &str,
trait_source: Option<String>,
) -> MethodDef {
let name = method.sig.ident.to_string();
let doc = extract_doc_comments(&method.attrs);
let mut is_async = method.sig.asyncness.is_some();
let (mut return_type, error_type, returns_ref) = resolve_return_type(&method.sig.output);
let returns_cow = detect_cow_return(&method.sig.output);
if !is_async {
if let Some(inner) = unwrap_future_return(&method.sig.output) {
is_async = true;
return_type = inner;
}
}
resolve_self_refs(&mut return_type, parent_type_name);
let (receiver, is_static) = detect_receiver(&method.sig.inputs);
let mut params = extract_params(&method.sig.inputs);
for param in &mut params {
resolve_self_refs(&mut param.ty, parent_type_name);
}
MethodDef {
name,
params,
return_type,
is_async,
is_static,
error_type,
doc,
receiver,
sanitized: false,
trait_source,
returns_ref,
returns_cow,
return_newtype_wrapper: None,
}
}
fn resolve_self_refs(ty: &mut TypeRef, parent_type_name: &str) {
match ty {
TypeRef::Named(n) if n == "Self" => *n = parent_type_name.to_string(),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => resolve_self_refs(inner, parent_type_name),
TypeRef::Map(k, v) => {
resolve_self_refs(k, parent_type_name);
resolve_self_refs(v, parent_type_name);
}
_ => {}
}
}
pub(crate) fn unwrap_future_return(output: &syn::ReturnType) -> Option<TypeRef> {
let ty = match output {
syn::ReturnType::Type(_, ty) => ty,
syn::ReturnType::Default => return None,
};
if let syn::Type::Path(type_path) = ty.as_ref() {
if let Some(seg) = type_path.path.segments.last() {
let ident = seg.ident.to_string();
match ident.as_str() {
"BoxFuture" | "BoxStream" => {
return extract_future_inner_type(seg);
}
"Pin" => {
return extract_pin_future_inner(seg);
}
_ => {}
}
}
}
None
}
fn extract_future_inner_type(segment: &syn::PathSegment) -> Option<TypeRef> {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(ty) = arg {
let resolved = type_resolver::resolve_type(ty);
return Some(resolved);
}
}
}
None
}
fn extract_pin_future_inner(segment: &syn::PathSegment) -> Option<TypeRef> {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(syn::Type::Path(inner_path)) = arg {
if let Some(inner_seg) = inner_path.path.segments.last() {
if inner_seg.ident == "Box" {
if let syn::PathArguments::AngleBracketed(box_args) = &inner_seg.arguments {
for box_arg in &box_args.args {
if let syn::GenericArgument::Type(syn::Type::TraitObject(trait_obj)) = box_arg {
return extract_future_output_from_trait_obj(trait_obj);
}
}
}
}
}
}
}
}
None
}
fn extract_future_output_from_trait_obj(trait_obj: &syn::TypeTraitObject) -> Option<TypeRef> {
for bound in &trait_obj.bounds {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
if let Some(seg) = trait_bound.path.segments.last() {
if seg.ident == "Future" {
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
for arg in &args.args {
if let syn::GenericArgument::AssocType(assoc) = arg {
if assoc.ident == "Output" {
return Some(type_resolver::resolve_type(&assoc.ty));
}
}
}
}
}
}
}
}
None
}
pub(crate) fn detect_receiver(
inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
) -> (Option<ReceiverKind>, bool) {
for input in inputs {
if let syn::FnArg::Receiver(recv) = input {
let kind = if recv.reference.is_some() {
if recv.mutability.is_some() {
ReceiverKind::RefMut
} else {
ReceiverKind::Ref
}
} else {
ReceiverKind::Owned
};
return (Some(kind), false);
}
}
(None, true)
}
fn option_inner_is_ref(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if let Some(seg) = type_path.path.segments.last() {
if seg.ident == "Option" {
if let Some(inner) = type_resolver::extract_single_generic_arg_syn(seg) {
return matches!(*inner, syn::Type::Reference(_));
}
}
}
}
false
}
pub(crate) fn extract_params(inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>) -> Vec<ParamDef> {
inputs
.iter()
.filter_map(|arg| {
if let syn::FnArg::Typed(pat_type) = arg {
let name = match &*pat_type.pat {
syn::Pat::Ident(ident) => ident.ident.to_string(),
_ => "_".to_string(),
};
let is_ref = matches!(&*pat_type.ty, syn::Type::Reference(_)) || option_inner_is_ref(&pat_type.ty);
let resolved = type_resolver::resolve_type(&pat_type.ty);
let (ty, optional) = unwrap_optional(resolved);
Some(ParamDef {
name,
ty,
optional,
default: None,
sanitized: false,
typed_default: None,
is_ref,
newtype_wrapper: None,
})
} else {
None }
})
.collect()
}
pub(crate) fn resolve_return_type(output: &syn::ReturnType) -> (TypeRef, Option<String>, bool) {
match output {
syn::ReturnType::Default => (TypeRef::Unit, None, false),
syn::ReturnType::Type(_, ty) => {
let error_type = type_resolver::extract_result_error_type(ty);
let inner_ty = if let Some(inner) = type_resolver::unwrap_result_type(ty) {
inner
} else {
ty.as_ref()
};
let unwrapped = unwrap_smart_pointer(inner_ty);
let returns_ref = syn_type_contains_ref(unwrapped) || is_cow_named_return(inner_ty);
let resolved = type_resolver::resolve_type(inner_ty);
(resolved, error_type, returns_ref)
}
}
}
fn unwrap_smart_pointer(ty: &syn::Type) -> &syn::Type {
if let syn::Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
let ident = segment.ident.to_string();
if matches!(ident.as_str(), "Box" | "Arc" | "Rc") {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner) = arg {
return inner;
}
}
}
}
}
}
ty
}
fn syn_type_contains_ref(ty: &syn::Type) -> bool {
match ty {
syn::Type::Reference(_) => true,
syn::Type::Path(type_path) => {
if let Some(segment) = type_path.path.segments.last() {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
return args.args.iter().any(|arg| {
if let syn::GenericArgument::Type(inner) = arg {
syn_type_contains_ref(inner)
} else {
false
}
});
}
}
false
}
_ => false,
}
}
fn detect_cow_return(output: &syn::ReturnType) -> bool {
if let syn::ReturnType::Type(_, ty) = output {
is_cow_named_return(ty)
} else {
false
}
}
fn is_cow_named_return(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "Cow" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner) = arg {
match inner {
syn::Type::Path(p) => {
if let Some(seg) = p.path.segments.last() {
return seg.ident != "str";
}
}
syn::Type::Slice(_) => return false,
_ => return true,
}
}
}
}
}
}
}
false
}