use crate::docs::extract_docs;
use crate::service::args::{FfiServiceArgs, ServiceExportKind};
use crate::skip::has_ffi_skip_attribute;
use proc_macro2::Span;
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::{FnArg, Generics, Ident, ImplItem, ItemImpl, Pat, PathSegment, ReturnType, Type, Visibility};
#[derive(Clone)]
#[allow(dead_code)]
pub struct ServiceModel {
pub service_name: Ident,
pub service_type: Type,
pub generics: syn::Generics,
pub args: FfiServiceArgs,
pub constructors: Vec<ServiceMethod>,
pub methods: Vec<ServiceMethod>,
pub is_async: bool,
}
#[derive(Clone)]
#[allow(dead_code)]
pub struct ServiceMethod {
pub name: Ident,
pub docs: Vec<String>,
pub inputs: Vec<ServiceParameter>,
pub output: ReturnType,
pub is_async: bool,
pub receiver_kind: ReceiverKind,
pub vis: Visibility,
pub span: Span,
pub skip: bool,
pub generics: Generics,
}
#[derive(Clone)]
#[allow(dead_code)]
pub struct ServiceParameter {
pub name: Ident,
pub ty: Type,
pub span: Span,
}
#[derive(Clone)]
pub enum ReceiverKind {
None, Shared, Mutable, AsyncThis, AsyncCtor(Box<Type>), }
impl ServiceModel {
#[allow(clippy::too_many_lines)]
pub fn from_impl_item(input: ItemImpl, args: FfiServiceArgs) -> syn::Result<Self> {
let service_type = match input.self_ty.as_ref() {
Type::Path(path) => {
if let Some(segment) = path.path.segments.last() {
(segment.ident.clone(), (*input.self_ty).clone())
} else {
return Err(syn::Error::new_spanned(&input.self_ty, "Invalid service type"));
}
}
_ => return Err(syn::Error::new_spanned(&input.self_ty, "Service type must be a path")),
};
let (service_name, service_type) = service_type;
let generics = input.generics.clone();
let mut constructors = Vec::new();
let mut methods = Vec::new();
let mut has_async = false;
for item in &input.items {
if let ImplItem::Fn(method) = item {
if has_ffi_skip_attribute(&method.attrs) {
continue;
}
let docs = extract_docs(&method.attrs);
let method_name = method.sig.ident.clone();
let is_async = method.sig.asyncness.is_some();
let vis = method.vis.clone();
let span = method.span();
if is_async {
has_async = true;
}
let mut inputs = Vec::new();
let mut receiver_kind = ReceiverKind::None;
let mut param_index = 0;
for (i, input_arg) in method.sig.inputs.iter().enumerate() {
match input_arg {
FnArg::Receiver(receiver) => {
receiver_kind = if receiver.mutability.is_some() {
ReceiverKind::Mutable
} else {
ReceiverKind::Shared
};
}
FnArg::Typed(typed_arg) => {
let param_type = (*typed_arg.ty).clone();
if i == 0
&& is_async
&& let Type::Path(path) = ¶m_type
&& let Some(segment) = path.path.segments.last()
&& segment.ident == "Async"
{
receiver_kind = receiver_kind_from_async_param(segment);
continue; }
let param_name = if let Pat::Ident(pat_ident) = typed_arg.pat.as_ref() {
pat_ident.ident.clone()
} else {
syn::Ident::new(&format!("_{param_index}"), typed_arg.pat.span())
};
inputs.push(ServiceParameter { name: param_name, ty: param_type, span: typed_arg.span() });
param_index += 1;
}
}
}
let service_method = ServiceMethod {
name: method_name,
docs,
inputs,
output: method.sig.output.clone(),
is_async,
receiver_kind: receiver_kind.clone(),
vis,
span,
skip: false,
generics: method.sig.generics.clone(),
};
if is_async {
match receiver_kind {
ReceiverKind::None => {
return Err(syn::Error::new_spanned(method.sig.inputs.first(), "Async methods must use Async<Self> as their first parameter"));
}
ReceiverKind::Shared | ReceiverKind::Mutable => {
let receiver_span = method.sig.inputs.first().map_or_else(|| method.sig.span(), Spanned::span);
return Err(syn::Error::new(receiver_span, "Async methods cannot use &self or &mut self. Use Async<Self> as the first parameter instead."));
}
ReceiverKind::AsyncThis => {
}
ReceiverKind::AsyncCtor(_) => {
}
}
}
match (&receiver_kind, is_async) {
(ReceiverKind::None, false) => constructors.push(service_method),
(ReceiverKind::AsyncCtor(_), true) => constructors.push(service_method),
_ => methods.push(service_method),
}
}
}
for param in &generics.params {
if let syn::GenericParam::Type(_) = param {
return Err(syn::Error::new_spanned(param, "Generic services are not supported by #[ffi], only lifetime work."));
}
}
let model = Self { service_name, service_type, generics, args, constructors, methods, is_async: has_async };
Ok(model)
}
pub fn service_name_snake_case(&self) -> String {
if let Some(ref prefix) = self.args.prefix {
prefix.trim_end_matches('_').to_string()
} else {
let name = self.service_name.to_string();
let mut result = String::new();
let mut chars = name.chars().peekable();
while let Some(ch) = chars.next() {
if ch.is_uppercase() && !result.is_empty() {
if let Some(&next_ch) = chars.peek()
&& next_ch.is_lowercase()
{
result.push('_');
}
}
result.push(ch.to_lowercase().next().unwrap_or(ch));
}
result
}
}
fn compute_service_hash(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.service_name.to_string().hash(&mut hasher);
for ctor in &self.constructors {
ctor.name.to_string().hash(&mut hasher);
for param in &ctor.inputs {
param.ty.to_token_stream().to_string().hash(&mut hasher);
}
match &ctor.output {
ReturnType::Default => "()".hash(&mut hasher),
ReturnType::Type(_, ty) => ty.to_token_stream().to_string().hash(&mut hasher),
}
}
for method in &self.methods {
method.name.to_string().hash(&mut hasher);
for param in &method.inputs {
param.ty.to_token_stream().to_string().hash(&mut hasher);
}
match &method.output {
ReturnType::Default => "()".hash(&mut hasher),
ReturnType::Type(_, ty) => ty.to_token_stream().to_string().hash(&mut hasher),
}
}
for param in &self.generics.params {
param.to_token_stream().to_string().hash(&mut hasher);
}
hasher.finish()
}
pub fn generate_export_name(&self, base_function_name: &str) -> Option<String> {
match &self.args.export {
Some(ServiceExportKind::Unique) => {
let hash = self.compute_service_hash();
Some(format!("{}_{}", base_function_name, hash % 100000))
}
None => None,
}
}
}
fn receiver_kind_from_async_param(segment: &PathSegment) -> ReceiverKind {
let Some(inner_type) = extract_async_inner_type(segment) else {
return ReceiverKind::AsyncThis;
};
if let Type::Path(inner_path) = inner_type
&& let Some(inner_segment) = inner_path.path.segments.last()
&& inner_segment.ident == "Self"
{
ReceiverKind::AsyncThis
} else {
ReceiverKind::AsyncCtor(Box::new(inner_type.clone()))
}
}
fn extract_async_inner_type(segment: &PathSegment) -> Option<&Type> {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments
&& let Some(syn::GenericArgument::Type(inner)) = args.args.first()
{
Some(inner)
} else {
None
}
}