use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{Expr, ItemFn, PatType, Receiver, Signature, Type, Visibility};
use crate::class::ReceiverMapping;
use crate::util::is_attr;
pub fn macro_impl(args: TokenStream, input: TokenStream) -> TokenStream {
if !args.is_empty() {
return syn::Error::new(Span::call_site(), "attribute does not accept any arguments")
.into_compile_error()
.into();
}
let crate_name = match proc_macro_crate::crate_name("hebi") {
Ok(found) => match found {
proc_macro_crate::FoundCrate::Itself => format_ident!("crate"),
proc_macro_crate::FoundCrate::Name(name) => format_ident!("{name}"),
},
Err(e) => {
return syn::Error::new(Span::call_site(), format!("{e}"))
.into_compile_error()
.into()
}
};
let input = syn::parse_macro_input!(input as syn::ItemFn);
let vis = input.vis.clone();
let sig = input.sig.clone();
let fn_info = match FnInfo::parse(vis, &sig) {
Ok(params) => params,
Err(e) => return e.into_compile_error().into(),
};
if let Some(receiver) = &fn_info.receiver {
return syn::Error::new(receiver.span(), "`self` is not supported")
.into_compile_error()
.into();
}
let mut input = input;
clear_sig_attrs(&mut input.sig);
Func {
crate_name: &crate_name,
out_fn_name: fn_info.name.clone(),
in_fn_name: None,
fn_info,
input_fn: Some(input),
type_name: None,
is_assoc_fn: false,
receiver_mapping: None,
}
.emit()
.into()
}
pub struct Func<'a> {
pub crate_name: &'a Ident,
pub out_fn_name: Ident,
pub in_fn_name: Option<Ident>,
pub fn_info: FnInfo,
pub input_fn: Option<ItemFn>,
pub type_name: Option<Ident>,
pub is_assoc_fn: bool,
pub receiver_mapping: Option<&'a ReceiverMapping>,
}
impl<'a> Func<'a> {
pub fn emit(self) -> TokenStream2 {
let Func {
crate_name,
out_fn_name,
in_fn_name,
fn_info,
input_fn,
type_name,
is_assoc_fn,
receiver_mapping,
} = self;
let input_fn_name = in_fn_name.unwrap_or_else(|| fn_info.name.clone());
let vis = fn_info.vis.clone();
let (input_mapping, arg_info) =
emit_input_mapping(crate_name, &fn_info, type_name.as_ref(), receiver_mapping);
let args = arg_info.call_args;
let assoc_ty_path = if is_assoc_fn {
let type_name = type_name.as_ref().unwrap();
Some(quote!(#type_name::))
} else {
None
};
let call = if fn_info.receiver.is_some() {
quote! {#assoc_ty_path #input_fn_name(this, #(#args),*)}
} else {
quote! {#assoc_ty_path #input_fn_name(#(#args),*)}
};
quote! {
#[allow(non_snake_case)]
#vis fn #out_fn_name<'hebi>(
ctx: &'hebi #crate_name::public::Context<'hebi>,
args: #crate_name::public::Args<'hebi>,
) -> #crate_name::Result<#crate_name::public::Value<'hebi>> {
#![allow(
clippy::unnecessary_lazy_evaluations,
clippy::absurd_extreme_comparisons,
unused_imports,
unused_variables,
dead_code
)]
use #crate_name::util::check_args;
use #crate_name::{FromHebi, FromHebiRef, IntoHebi};
#input_fn
#input_mapping
#call.into_hebi(ctx)
}
}
}
}
pub fn emit_input_mapping(
crate_name: &Ident,
params: &FnInfo,
type_name: Option<&Ident>,
receiver_mapping: Option<&ReceiverMapping>,
) -> (TokenStream2, ArgInfo) {
let from_hebi = format_ident!("from_hebi");
let from_hebi_ref = format_ident!("from_hebi_ref");
let positional_ref = quote!(&positional);
let positional_owned = quote!(positional);
let clone_call = Some(quote!(.clone()));
let no_clone_call = None;
let has_self = params.receiver.is_some();
let required_positional_params = params.required_positional().map(|v| v.name.to_string());
let max_positional_params = params.max_positional();
let keyword_params = params
.keyword
.iter()
.map(|v| (v.name.to_string(), v.default.is_some()))
.map(|(k, r)| quote! {(#k, #r)});
let positional_param_mapping = params
.positional
.iter()
.enumerate()
.map(|(i, p)| {
let name = format_ident!("_pos_{i}");
let ty = &p.ty;
let (from_fn, args, clone_call) = if !is_ref(ty) {
(&from_hebi, &positional_owned, &clone_call)
} else {
(&from_hebi_ref, &positional_ref, &no_clone_call)
};
let init = match &p.default {
Some(v) => quote! {
if positional.len() <= #i {
#v
} else {
<#ty>::#from_fn(ctx, #args[#i]#clone_call)?
}
},
None => quote! {
<#ty>::#from_fn(ctx, #args[#i]#clone_call)?
},
};
(quote! {let #name = #init;}, name)
})
.collect::<Vec<_>>();
let keyword_param_mapping = params
.keyword
.iter()
.enumerate()
.map(|(i, p)| {
let name = format_ident!("_kw_{i}");
let key = p.name.to_string();
let ty = &p.ty;
let (from_fn, clone_call) = if !is_ref(ty) {
(&from_hebi, &clone_call)
} else {
(&from_hebi_ref, &no_clone_call)
};
let init = match &p.default {
Some(v) => quote! {
if let Some(value) = keyword.as_ref().and_then(|kw| kw.get(#key)) {
<#ty>::#from_fn(ctx, value #clone_call)?
} else {
#v
}
},
None => quote! {
<#ty>::#from_fn(ctx, keyword.as_ref().and_then(|kw| kw.get(#key)).unwrap() #clone_call)?
},
};
(quote! {let #name = #init;}, name)
})
.collect::<Vec<_>>();
let this_mapping = match params.receiver.as_ref().map(|r| r.mutability) {
Some(m) => {
let is_mut = m.is_some();
if let Some(mapping) = receiver_mapping {
match is_mut {
true => mapping.mut_.clone(),
false => mapping.ref_.clone(),
}
} else {
let cast_error_msg = format!(
"receiver is not an instance of {}",
type_name.as_ref().unwrap()
);
let cast = match is_mut {
true => format_ident!("cast_mut"),
false => format_ident!("cast"),
};
quote! {
let mut args = args;
let args = args.resolve_receiver()?;
let mut this = match args.this().as_user_data() {
Some(this) => this,
None => return Err(#crate_name::Error::runtime(#cast_error_msg)),
};
let mut this = match unsafe { this.#cast::<#type_name>() } {
Some(this) => this,
None => return Err(#crate_name::Error::runtime(#cast_error_msg)),
};
}
}
}
_ => TokenStream2::new(),
};
let input_mapping = positional_param_mapping
.iter()
.map(|(t, _)| t)
.chain(keyword_param_mapping.iter().map(|(t, _)| t));
let args = positional_param_mapping
.iter()
.map(|(_, i)| i)
.chain(keyword_param_mapping.iter().map(|(_, i)| i))
.collect::<Vec<_>>();
let out_args = ArgInfo {
call_args: args.iter().map(|&i| i.clone()).collect(),
};
(
quote! {
check_args(
&args,
#has_self,
&[#(#required_positional_params),*],
#max_positional_params,
&[#(#keyword_params),*],
)?;
#this_mapping
let positional = args.positional();
let keyword = args.keyword();
#(#input_mapping)*
},
out_args,
)
}
pub fn clear_sig_attrs(sig: &mut Signature) {
for input in sig.inputs.iter_mut() {
match input {
syn::FnArg::Receiver(Receiver { attrs, .. }) | syn::FnArg::Typed(PatType { attrs, .. }) => {
*attrs = attrs
.iter()
.cloned()
.filter(|a| !is_attr(a, &["kw", "default"]))
.collect()
}
}
}
}
pub struct ArgInfo {
pub call_args: Vec<Ident>,
}
pub struct FnInfo {
pub name: Ident,
pub vis: Visibility,
pub receiver: Option<Receiver>,
pub positional: Vec<Param>,
pub keyword: Vec<Param>,
}
pub struct Param {
pub name: Ident,
pub ty: Type,
pub default: Option<Expr>,
}
impl FnInfo {
pub fn required_positional(&self) -> impl Iterator<Item = &Param> {
self.positional.iter().filter(|v| v.default.is_none())
}
pub fn max_positional(&self) -> usize {
self.positional.len()
}
}
impl FnInfo {
pub fn parse(vis: Visibility, sig: &Signature) -> syn::Result<Self> {
if !sig.generics.params.is_empty() {
return Err(syn::Error::new(
sig.generics.span(),
"generics are not supported",
));
}
let name = sig.ident.clone();
let inputs = &sig.inputs;
let mut receiver = None;
let mut positional = vec![];
let mut keyword = vec![];
enum State {
Positional,
PositionalDefault,
Keyword,
}
let mut state = State::Positional;
for param in inputs.iter() {
match param {
syn::FnArg::Receiver(r) => {
if r.reference.is_none() {
return Err(syn::Error::new(
r.span(),
"receiver must be taken by reference",
));
}
receiver = Some(r.clone());
}
syn::FnArg::Typed(r) => {
let name = get_name(r)?;
let ty = r.ty.as_ref().clone();
let default = get_default(r)?;
if !is_keyword(r) {
if matches!(state, State::Keyword) {
return Err(syn::Error::new(
name.span(),
"positional parameters may not appear after keyword parameters",
));
}
if default.is_some() {
state = State::PositionalDefault;
} else if matches!(state, State::PositionalDefault) {
return Err(syn::Error::new(
name.span(),
"non-default positional parameters may not appear after default positional parameters",
));
}
} else {
state = State::Keyword;
}
let param = Param { name, ty, default };
if is_keyword(r) {
keyword.push(param);
} else {
positional.push(param)
}
}
}
}
Ok(FnInfo {
name,
vis,
receiver,
positional,
keyword,
})
}
}
pub fn get_name(param: &PatType) -> syn::Result<Ident> {
if let syn::Pat::Ident(pat) = &*param.pat {
Ok(pat.ident.clone())
} else {
Err(syn::Error::new(
param.pat.span(),
"param pattern must be an identifier",
))
}
}
pub fn is_keyword(param: &PatType) -> bool {
param.attrs.iter().any(|v| v.path.is_ident("kw"))
}
pub fn is_option(param: &PatType) -> bool {
match &*param.ty {
Type::Path(ty) if ty.path.segments.len() == 1 => match ty.path.segments.first() {
Some(segment) => segment.ident == "Option" && !segment.arguments.is_empty(),
None => false,
},
_ => false,
}
}
pub fn get_default(param: &PatType) -> syn::Result<Option<Expr>> {
let default = param
.attrs
.iter()
.find(|v| v.path.is_ident("default"))
.map(|v| v.parse_args())
.transpose()?;
if default.is_none() && is_option(param) {
Ok(Some(syn::parse_quote!(None)))
} else {
Ok(default)
}
}
pub fn is_ref(ty: &Type) -> bool {
match ty {
Type::Path(ty) if ty.path.segments.len() == 1 => match ty.path.segments.first() {
Some(syn::PathSegment {
ident,
arguments: syn::PathArguments::AngleBracketed(inner),
}) => {
ident == "Option"
&& inner
.args
.first()
.and_then(|v| match v {
syn::GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.map(is_direct_ref)
.unwrap_or(false)
}
_ => false,
},
Type::Reference(_) => true,
_ => false,
}
}
pub fn is_direct_ref(ty: &Type) -> bool {
matches!(ty, Type::Reference(_))
}