use crate::core::ir::ApiSurface;
use crate::core::ir::{FunctionDef, MethodDef, ParamDef, ReceiverKind, TypeDef, TypeRef, UnsupportedPublicItem};
use ahash::AHashMap;
use crate::extract::type_resolver;
use super::defaults::extract_default_values;
use super::helpers::{
build_rust_path, extract_binding_exclusion_reason, extract_cfg_condition, extract_doc_comments, unwrap_optional,
};
fn has_non_lifetime_generics(generics: &syn::Generics) -> bool {
generics
.params
.iter()
.any(|param| !matches!(param, syn::GenericParam::Lifetime(_)))
}
fn record_unsupported_generic_impl_methods(
item: &syn::ItemImpl,
crate_name: &str,
type_name: &str,
surface: &mut ApiSurface,
reason: &str,
methods_are_public_by_trait: bool,
) {
for impl_item in &item.items {
let syn::ImplItem::Fn(method) = impl_item else {
continue;
};
if (!methods_are_public_by_trait && !super::helpers::is_pub(&method.vis))
|| extract_binding_exclusion_reason(&method.attrs).is_some()
{
continue;
}
let method_name = method.sig.ident.to_string();
if method_name.starts_with('_') {
continue;
}
surface.unsupported_public_items.push(UnsupportedPublicItem {
item_kind: "method".to_string(),
item_path: format!("{crate_name}::{type_name}.{method_name}"),
reason: reason.to_string(),
suggested_fix:
"exclude the method, configure an opaque/bridge policy, or provide explicit monomorphization metadata"
.to_string(),
});
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum AsRefTarget {
Str,
Path,
Bytes,
}
impl AsRefTarget {
fn scalar_type_ref(self) -> TypeRef {
match self {
AsRefTarget::Str => TypeRef::String,
AsRefTarget::Path => TypeRef::Path,
AsRefTarget::Bytes => TypeRef::Bytes,
}
}
fn vec_elem_type_ref(self) -> TypeRef {
match self {
AsRefTarget::Str => TypeRef::String,
AsRefTarget::Path => TypeRef::Path,
AsRefTarget::Bytes => TypeRef::Bytes,
}
}
}
pub(crate) fn detect_asref_single_generic(generics: &syn::Generics) -> Option<(String, AsRefTarget)> {
let non_lifetime_params: Vec<&syn::GenericParam> = generics
.params
.iter()
.filter(|p| !matches!(p, syn::GenericParam::Lifetime(_)))
.collect();
if non_lifetime_params.len() != 1 {
return None;
}
let syn::GenericParam::Type(type_param) = non_lifetime_params[0] else {
return None; };
let generic_name = type_param.ident.to_string();
let param_bounds: Vec<&syn::TypeParamBound> = type_param.bounds.iter().collect();
let where_bounds: Vec<&syn::TypeParamBound> = generics
.where_clause
.iter()
.flat_map(|wc| &wc.predicates)
.filter_map(|pred| {
if let syn::WherePredicate::Type(pred_type) = pred {
if let syn::Type::Path(p) = &pred_type.bounded_ty {
if p.path.is_ident(&generic_name) {
return Some(pred_type.bounds.iter().collect::<Vec<_>>());
}
}
}
None
})
.flatten()
.collect();
let all_bounds: Vec<&syn::TypeParamBound> = param_bounds.into_iter().chain(where_bounds).collect();
if all_bounds.len() != 1 {
return None;
}
let syn::TypeParamBound::Trait(trait_bound) = all_bounds[0] else {
return None;
};
let seg = trait_bound.path.segments.last()?;
if seg.ident != "AsRef" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
return None;
};
let type_args: Vec<&syn::GenericArgument> = args.args.iter().collect();
if type_args.len() != 1 {
return None;
}
let syn::GenericArgument::Type(inner) = type_args[0] else {
return None;
};
let target = match inner {
syn::Type::Path(p) => {
let ident = p.path.segments.last()?.ident.to_string();
if ident == "str" {
AsRefTarget::Str
} else if ident == "Path" {
AsRefTarget::Path
} else {
return None;
}
}
syn::Type::Slice(slice) => {
if let syn::Type::Path(p) = &*slice.elem {
if p.path.is_ident("u8") {
AsRefTarget::Bytes
} else {
return None;
}
} else {
return None;
}
}
_ => return None,
};
Some((generic_name, target))
}
pub(crate) fn try_extract_asref_monomorphized(
item: &syn::ItemFn,
crate_name: &str,
module_path: &str,
) -> Option<FunctionDef> {
let (generic_name, target) = detect_asref_single_generic(&item.sig.generics)?;
let params = extract_params_with_asref_substitution(&item.sig.inputs, &generic_name, target)?;
let binding_exclusion_reason = extract_binding_exclusion_reason(&item.attrs);
let binding_excluded = binding_exclusion_reason.is_some();
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, mut error_type, returns_ref) = resolve_return_type(&item.sig.output);
let returns_cow = detect_cow_return(&item.sig.output);
if !is_async {
let empty = ahash::AHashSet::new();
if let Some((inner, future_error_type)) = unwrap_future_return(&item.sig.output, &empty) {
is_async = true;
return_type = inner;
if future_error_type.is_some() {
error_type = future_error_type;
}
}
}
let rust_path = build_rust_path(crate_name, module_path, &name);
let sanitized = params.iter().any(|p| p.sanitized);
Some(FunctionDef {
rust_path,
original_rust_path: String::new(),
name,
params,
return_type,
is_async,
error_type,
doc,
cfg,
sanitized,
return_sanitized: false,
returns_ref,
returns_cow,
return_newtype_wrapper: None,
binding_excluded,
binding_exclusion_reason,
})
}
fn try_substitute_param(
name: String,
ty: &syn::Type,
generic_name: &str,
target: AsRefTarget,
) -> Option<SubstituteResult> {
if !syn_type_involves_generic(ty, generic_name) {
return Some(SubstituteResult::PassThrough);
}
if let syn::Type::Reference(r) = ty {
if let syn::Type::Slice(slice) = &*r.elem {
if is_bare_generic(&slice.elem, generic_name) {
let elem = target.vec_elem_type_ref();
return Some(SubstituteResult::Monomorphized(ParamDef {
name,
ty: TypeRef::Vec(Box::new(elem)),
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: true,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: true,
}));
}
}
}
if let syn::Type::Path(p) = ty {
if let Some(seg) = p.path.segments.last() {
if seg.ident == "Vec" {
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
let type_args: Vec<_> = args
.args
.iter()
.filter_map(|a| {
if let syn::GenericArgument::Type(t) = a {
Some(t)
} else {
None
}
})
.collect();
if type_args.len() == 1 && is_bare_generic(type_args[0], generic_name) {
let elem = target.vec_elem_type_ref();
return Some(SubstituteResult::Monomorphized(ParamDef {
name,
ty: TypeRef::Vec(Box::new(elem)),
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: false,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
}));
}
}
}
}
}
if is_bare_generic(ty, generic_name) {
let scalar = target.scalar_type_ref();
return Some(SubstituteResult::Monomorphized(ParamDef {
name,
ty: scalar,
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: false,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
}));
}
if let syn::Type::Reference(r) = ty {
if is_bare_generic(&r.elem, generic_name) {
let scalar = target.scalar_type_ref();
return Some(SubstituteResult::Monomorphized(ParamDef {
name,
ty: scalar,
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: true,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
}));
}
}
None
}
enum SubstituteResult {
PassThrough,
Monomorphized(ParamDef),
}
fn is_bare_generic(ty: &syn::Type, generic_name: &str) -> bool {
if let syn::Type::Path(p) = ty {
p.qself.is_none() && p.path.is_ident(generic_name)
} else {
false
}
}
fn syn_type_involves_generic(ty: &syn::Type, generic_name: &str) -> bool {
match ty {
syn::Type::Path(p) => {
if p.path.is_ident(generic_name) {
return true;
}
p.path.segments.iter().any(|seg| {
if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
args.args.iter().any(|arg| {
if let syn::GenericArgument::Type(inner) = arg {
syn_type_involves_generic(inner, generic_name)
} else {
false
}
})
} else {
false
}
})
}
syn::Type::Reference(r) => syn_type_involves_generic(&r.elem, generic_name),
syn::Type::Slice(s) => syn_type_involves_generic(&s.elem, generic_name),
_ => false,
}
}
fn extract_params_with_asref_substitution(
inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
generic_name: &str,
target: AsRefTarget,
) -> Option<Vec<ParamDef>> {
let mut result = Vec::new();
for arg in inputs {
let syn::FnArg::Typed(pat_type) = arg else {
continue; };
let name = match &*pat_type.pat {
syn::Pat::Ident(ident) => ident.ident.to_string(),
_ => "_".to_string(),
};
match try_substitute_param(name.clone(), &pat_type.ty, generic_name, target)? {
SubstituteResult::PassThrough => {
use super::helpers::unwrap_optional;
let is_ref = matches!(&*pat_type.ty, syn::Type::Reference(_)) || option_inner_is_ref(&pat_type.ty);
let is_mut = is_mut_ref(&pat_type.ty);
let resolved = type_resolver::resolve_type(&pat_type.ty);
let (map_is_ahash, map_key_is_cow) = detect_map_metadata(&pat_type.ty);
let sanitized = is_tuple_type(&resolved);
let original_type = if sanitized {
Some(format!("{:?}", resolved))
} else {
None
};
let (ty, optional) = unwrap_optional(resolved);
result.push(ParamDef {
name,
ty,
optional,
default: None,
sanitized,
typed_default: None,
is_ref,
is_mut,
newtype_wrapper: None,
original_type,
map_is_ahash,
map_key_is_cow,
vec_inner_is_ref: vec_inner_is_ref(&pat_type.ty),
});
}
SubstituteResult::Monomorphized(param) => {
result.push(param);
}
}
}
Some(result)
}
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 binding_exclusion_reason = extract_binding_exclusion_reason(&item.attrs);
let binding_excluded = binding_exclusion_reason.is_some();
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, mut error_type, returns_ref) = resolve_return_type(&item.sig.output);
let returns_cow = detect_cow_return(&item.sig.output);
if !is_async {
let empty = ahash::AHashSet::new();
if let Some((inner, future_error_type)) = unwrap_future_return(&item.sig.output, &empty) {
is_async = true;
return_type = inner;
if future_error_type.is_some() {
error_type = future_error_type;
}
}
}
let params = extract_params(&item.sig.inputs);
let rust_path = build_rust_path(crate_name, module_path, &name);
let sanitized = params.iter().any(|p| p.sanitized);
Some(FunctionDef {
rust_path,
original_rust_path: String::new(),
name,
params,
return_type,
is_async,
error_type,
doc,
cfg,
sanitized,
return_sanitized: false,
returns_ref,
returns_cow,
return_newtype_wrapper: None,
binding_excluded,
binding_exclusion_reason,
})
}
pub(crate) fn extract_impl_block(
item: &syn::ItemImpl,
crate_name: &str,
module_path: &str,
surface: &mut ApiSurface,
type_index: &AHashMap<String, usize>,
result_wrapping_aliases: &ahash::AHashSet<String>,
) {
if item.trait_.is_some() {
extract_trait_impl_methods(item, crate_name, surface, type_index, result_wrapping_aliases);
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,
};
if has_non_lifetime_generics(&item.generics) {
record_unsupported_generic_impl_methods(
item,
crate_name,
&type_name,
surface,
"public methods on generic impl blocks cannot be represented without explicit monomorphization metadata",
false,
);
return;
}
let type_is_opaque = item.generics.params.is_empty()
&& (type_index
.get(&type_name)
.map(|&idx| surface.types[idx].is_opaque)
.unwrap_or(false)
|| surface.enums.iter().any(|e| e.name == type_name)
|| surface.errors.iter().any(|e| e.name == type_name)
|| !type_index.contains_key(&type_name));
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() {
if extract_binding_exclusion_reason(&method.attrs).is_none() {
surface.unsupported_public_items.push(UnsupportedPublicItem {
item_kind: "method".to_string(),
item_path: format!("{crate_name}::{type_name}.{}", method.sig.ident),
reason: "public generic inherent methods cannot be represented without explicit monomorphization metadata".to_string(),
suggested_fix: "exclude the method, configure an opaque/bridge policy, or provide explicit monomorphization metadata".to_string(),
});
}
return None;
}
let method_name = method.sig.ident.to_string();
if method_name.starts_with('_') {
return None;
}
if method_name == "new" && !type_is_opaque {
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,
result_wrapping_aliases,
));
}
}
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 if let Some(error_def) = surface.errors.iter_mut().find(|e| e.name == type_name) {
const ERROR_METHOD_WHITELIST: &[&str] = &["status_code", "is_transient", "error_type"];
for method in methods {
let is_whitelisted = ERROR_METHOD_WHITELIST.contains(&method.name.as_str());
let already_present = error_def.methods.iter().any(|m| m.name == method.name);
if is_whitelisted && !already_present {
error_def.methods.push(method);
}
}
} else if surface.enums.iter().any(|e| e.name == type_name) {
} else {
let rust_path = build_rust_path(crate_name, module_path, &type_name);
surface.types.push(TypeDef {
name: type_name.clone(),
rust_path,
original_rust_path: String::new(),
fields: vec![],
methods,
is_opaque: true,
is_clone: false,
is_copy: 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,
super_traits: vec![],
binding_excluded: true,
binding_exclusion_reason: Some(
"synthetic-opaque-from-impl-block (source visibility unverified)".to_string(),
),
is_variant_wrapper: false,
has_lifetime_params: false,
});
}
}
pub(crate) fn extract_trait_impl_methods(
item: &syn::ItemImpl,
crate_name: &str,
surface: &mut ApiSurface,
type_index: &AHashMap<String, usize>,
result_wrapping_aliases: &ahash::AHashSet<String>,
) {
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;
};
if has_non_lifetime_generics(&item.generics) {
record_unsupported_generic_impl_methods(
item,
crate_name,
&type_name,
surface,
"public trait implementation methods on generic impl blocks cannot be represented without explicit monomorphization metadata",
true,
);
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);
}
}
let is_conversion_trait = item.trait_.as_ref().is_some_and(|(_, path, _)| {
path.segments
.last()
.is_some_and(|s| matches!(s.ident.to_string().as_str(), "From" | "Into" | "TryFrom" | "TryInto"))
});
if is_conversion_trait {
return;
}
let is_std_trait_impl = item.trait_.as_ref().is_some_and(|(_, path, _)| {
path.segments
.last()
.is_some_and(|s| STD_TRAITS.contains(&s.ident.to_string().as_str()))
});
for impl_item in &item.items {
if let syn::ImplItem::Fn(method) = impl_item {
if !method.sig.generics.params.is_empty() {
if !is_std_trait_impl && extract_binding_exclusion_reason(&method.attrs).is_none() {
surface.unsupported_public_items.push(UnsupportedPublicItem {
item_kind: "method".to_string(),
item_path: format!("{crate_name}::{type_name}.{}", method.sig.ident),
reason: "public generic trait implementation methods cannot be represented without explicit monomorphization metadata".to_string(),
suggested_fix: "exclude the method, configure an opaque/bridge policy, or provide explicit monomorphization metadata".to_string(),
});
}
continue;
}
let method_def = extract_method(
method,
crate_name,
&type_name,
trait_source.clone(),
result_wrapping_aliases,
);
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>,
result_wrapping_aliases: &ahash::AHashSet<String>,
) -> MethodDef {
let name = method.sig.ident.to_string();
let doc = extract_doc_comments(&method.attrs);
let binding_exclusion_reason = extract_binding_exclusion_reason(&method.attrs);
let binding_excluded = binding_exclusion_reason.is_some();
let mut is_async = method.sig.asyncness.is_some();
let (mut return_type, mut 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, future_error_type)) = unwrap_future_return(&method.sig.output, result_wrapping_aliases) {
is_async = true;
return_type = inner;
if future_error_type.is_some() {
error_type = future_error_type;
}
}
}
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,
has_default_impl: false,
binding_excluded,
binding_exclusion_reason,
}
}
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,
result_wrapping_aliases: &ahash::AHashSet<String>,
) -> Option<(TypeRef, Option<String>)> {
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" => {
let result = extract_future_inner_type(seg)?;
if result.1.is_none() && result_wrapping_aliases.contains(&ident) {
return Some((result.0, Some("Error".to_string())));
}
return Some(result);
}
"Pin" => {
return extract_pin_future_inner(seg);
}
_ => {}
}
}
}
None
}
fn resolve_possibly_result_type(ty: &syn::Type) -> (TypeRef, Option<String>) {
let error_type = type_resolver::extract_result_error_type(ty);
let inner = if let Some(unwrapped) = type_resolver::unwrap_result_type(ty) {
unwrapped
} else {
ty
};
(type_resolver::resolve_type(inner), error_type)
}
fn extract_future_inner_type(segment: &syn::PathSegment) -> Option<(TypeRef, Option<String>)> {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(ty) = arg {
return Some(resolve_possibly_result_type(ty));
}
}
}
None
}
fn extract_pin_future_inner(segment: &syn::PathSegment) -> Option<(TypeRef, Option<String>)> {
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, Option<String>)> {
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(resolve_possibly_result_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 detect_map_metadata(ty: &syn::Type) -> (bool, bool) {
let map_seg = find_map_segment(ty);
let Some(seg) = map_seg else {
return (false, false);
};
let ident = seg.ident.to_string();
let map_is_ahash = ident == "AHashMap";
let map_key_is_cow = if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
args.args
.iter()
.find_map(|a| {
if let syn::GenericArgument::Type(syn::Type::Path(tp)) = a {
tp.path.segments.last().map(|s| s.ident == "Cow")
} else {
None
}
})
.unwrap_or(false)
} else {
false
};
(map_is_ahash, map_key_is_cow)
}
fn find_map_segment(ty: &syn::Type) -> Option<&syn::PathSegment> {
match ty {
syn::Type::Reference(r) => find_map_segment(&r.elem),
syn::Type::Path(tp) => {
let seg = tp.path.segments.last()?;
let name = seg.ident.to_string();
match name.as_str() {
"HashMap" | "BTreeMap" | "AHashMap" | "IndexMap" | "FxHashMap" => Some(seg),
"Option" | "Box" | "Arc" | "Rc" => {
if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
for arg in &ab.args {
if let syn::GenericArgument::Type(inner) = arg {
return find_map_segment(inner);
}
}
}
None
}
_ => None,
}
}
_ => None,
}
}
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
}
fn is_mut_ref(ty: &syn::Type) -> bool {
match ty {
syn::Type::Reference(r) => r.mutability.is_some(),
syn::Type::Path(type_path) => {
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) {
if let syn::Type::Reference(r) = &*inner {
return r.mutability.is_some();
}
}
}
}
false
}
_ => false,
}
}
fn is_tuple_type(ty: &TypeRef) -> bool {
match ty {
TypeRef::Named(n) => n.starts_with('('),
TypeRef::Vec(inner) => is_tuple_type(inner),
TypeRef::Optional(inner) => is_tuple_type(inner),
_ => false,
}
}
fn vec_inner_is_ref(ty: &syn::Type) -> bool {
let deref_ty = if let syn::Type::Reference(r) = ty {
r.elem.as_ref()
} else {
ty
};
let to_check = if let syn::Type::Path(type_path) = deref_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) {
inner
} else {
return false;
}
} else {
Box::new(deref_ty.clone())
}
} else {
return false;
}
} else {
Box::new(deref_ty.clone())
};
match to_check.as_ref() {
syn::Type::Slice(slice) => matches!(*slice.elem, syn::Type::Reference(_)),
syn::Type::Path(type_path) => {
if let Some(seg) = type_path.path.segments.last() {
if seg.ident == "Vec" {
if let Some(elem_type) = type_resolver::extract_single_generic_arg_syn(seg) {
matches!(*elem_type, syn::Type::Reference(_))
} else {
false
}
} else {
false
}
} else {
false
}
}
_ => 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 is_mut = is_mut_ref(&pat_type.ty);
let resolved = type_resolver::resolve_type(&pat_type.ty);
let (map_is_ahash, map_key_is_cow) = detect_map_metadata(&pat_type.ty);
let sanitized = is_tuple_type(&resolved);
let original_type = if sanitized {
Some(format!("{:?}", resolved))
} else {
None
};
let (ty, optional) = unwrap_optional(resolved);
Some(ParamDef {
name,
ty,
optional,
default: None,
sanitized,
typed_default: None,
is_ref,
is_mut,
newtype_wrapper: None,
original_type,
map_is_ahash,
map_key_is_cow,
vec_inner_is_ref: vec_inner_is_ref(&pat_type.ty),
})
} 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
}