use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned};
use crate::class::RpcAttr;
use crate::util::{bail, bail_fn, ident, safe_ident, to_spanned_tuple};
use crate::{ParseResult, util};
pub struct FuncDefinition {
pub signature_info: SignatureInfo,
pub external_attributes: Vec<venial::Attribute>,
pub registered_name: Option<String>,
pub is_script_virtual: bool,
pub rpc_info: Option<RpcAttr>,
pub is_generated_accessor: bool,
}
impl FuncDefinition {
pub fn rust_ident(&self) -> &Ident {
&self.signature_info.method_name
}
pub fn godot_name(&self) -> String {
if let Some(name_override) = self.registered_name.as_ref() {
name_override.clone()
} else {
self.rust_ident().to_string()
}
}
}
pub fn make_virtual_callback(
class_name: &Ident,
trait_base_class: &Ident,
signature_info: &SignatureInfo,
before_kind: BeforeKind,
interface_trait: Option<&venial::TypeExpr>,
) -> TokenStream {
let method_name = &signature_info.method_name;
let wrapped_method = make_forwarding_closure(
class_name,
trait_base_class,
signature_info,
before_kind,
interface_trait,
);
let sig_params = signature_info.params_type();
let sig_ret = &signature_info.return_type;
let call_ctx = make_call_context(
class_name.to_string().as_str(),
method_name.to_string().as_str(),
);
let invocation = make_ptrcall_invocation(&wrapped_method, true);
quote! {
{
use ::godot::sys;
type CallParams = #sig_params;
type CallRet = #sig_ret;
unsafe extern "C" fn virtual_fn(
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstTypePtr,
ret: sys::GDExtensionTypePtr,
) {
let call_ctx = #call_ctx;
::godot::private::handle_fallible_ptrcall(
&call_ctx,
|| #invocation
);
}
Some(virtual_fn)
}
}
}
pub fn make_method_registration(
class_name: &Ident,
func_definition: FuncDefinition,
interface_trait: Option<&venial::TypeExpr>,
) -> ParseResult<TokenStream> {
let class_name = ident(&class_name.to_string());
let signature_info = &func_definition.signature_info;
let sig_params = signature_info.params_type();
let sig_ret = &signature_info.return_type;
let is_script_virtual = func_definition.is_script_virtual;
let method_flags = match make_method_flags(signature_info.receiver_type, is_script_virtual) {
Ok(mf) => mf,
Err(msg) => return bail_fn(msg, &signature_info.method_name),
};
let forwarding_closure = make_forwarding_closure(
&class_name,
&class_name, signature_info,
BeforeKind::Without,
interface_trait,
);
let default_parameters = make_default_argument_vec(
&signature_info.optional_param_default_exprs,
&signature_info.param_types,
)?;
let class_name_str = class_name.to_string();
let method_name_str = func_definition.godot_name();
let call_ctx = make_call_context(&class_name_str, &method_name_str);
let varcall_fn_decl = make_varcall_fn(&call_ctx, &forwarding_closure, &default_parameters);
let ptrcall_fn_decl = make_ptrcall_fn(&call_ctx, &forwarding_closure);
let param_ident_strs = signature_info
.param_idents
.iter()
.map(|ident| ident.to_string());
let cfg_attrs = util::extract_cfg_attrs(&func_definition.external_attributes)
.into_iter()
.collect::<Vec<_>>();
let type_and_bounds_check = if func_definition.is_generated_accessor {
TokenStream::new()
} else {
let sig_ret_span = signature_info.params_span;
quote_spanned! { sig_ret_span=>
::godot::private::ensure_func_bounds::<CallParams, CallRet>();
}
};
let registration = quote! {
#(#cfg_attrs)*
{
use ::godot::obj::GodotClass;
use ::godot::register::private::method::ClassMethodInfo;
use ::godot::builtin::{StringName, Variant};
use ::godot::sys;
type CallParams = #sig_params;
type CallRet = #sig_ret;
#type_and_bounds_check
let method_name = StringName::from(#method_name_str);
#varcall_fn_decl;
#ptrcall_fn_decl;
let method_info = unsafe {
ClassMethodInfo::from_signature::<#class_name, CallParams, CallRet>(
method_name,
Some(varcall_fn),
Some(ptrcall_fn),
#method_flags,
&[
#( #param_ident_strs ),*
],
#default_parameters,
)
};
::godot::private::out!(
" Register fn: {}::{}",
#class_name_str,
#method_name_str
);
method_info.register_extension_class_method();
};
};
Ok(registration)
}
fn make_default_argument_vec(
optional_param_default_exprs: &[TokenStream],
all_params: &[venial::TypeExpr],
) -> ParseResult<TokenStream> {
if optional_param_default_exprs.is_empty() {
return Ok(quote! { vec![] });
}
let optional_param_types = all_params
.iter()
.skip(all_params.len() - optional_param_default_exprs.len());
let default_parameters = optional_param_default_exprs
.iter()
.zip(optional_param_types)
.map(|(value, param_type)| {
quote! {
::godot::private::opt_default_value::<#param_type>(#value)
}
});
Ok(quote! {
vec![ #(#default_parameters),* ]
})
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum ReceiverType {
Ref,
Mut,
GdSelf,
Static,
}
#[derive(Debug)]
pub struct SignatureInfo {
pub method_name: Ident,
pub receiver_type: ReceiverType,
pub params_span: Span,
pub param_idents: Vec<Ident>,
pub param_types: Vec<venial::TypeExpr>,
pub return_type: TokenStream,
pub modified_param_types: Vec<(usize, venial::TypeExpr)>,
pub optional_param_default_exprs: Vec<TokenStream>,
}
impl SignatureInfo {
pub fn fn_ready() -> Self {
Self {
method_name: ident("ready"),
receiver_type: ReceiverType::Mut,
params_span: Span::call_site(),
param_idents: vec![],
param_types: vec![],
return_type: quote! { () },
modified_param_types: vec![],
optional_param_default_exprs: vec![],
}
}
pub fn params_tuple(&self) -> Group {
to_spanned_tuple(&self.param_idents, self.params_span)
}
pub fn params_type(&self) -> Group {
to_spanned_tuple(&self.param_types, self.params_span)
}
}
#[derive(Copy, Clone)]
pub enum BeforeKind {
Without,
WithBefore,
OnlyBefore,
}
fn make_forwarding_closure(
class_name: &Ident,
trait_base_class: &Ident,
signature_info: &SignatureInfo,
before_kind: BeforeKind,
interface_trait: Option<&venial::TypeExpr>,
) -> TokenStream {
let method_name = &signature_info.method_name;
let params = &signature_info.param_idents;
let params_tuple = signature_info.params_tuple();
let param_ident = Ident::new("params", signature_info.params_span);
let instance_decl = match &signature_info.receiver_type {
ReceiverType::Ref => quote! {
let __gdext_self = ::godot::private::Storage::get(storage);
},
ReceiverType::Mut => quote! {
let mut __gdext_self = ::godot::private::Storage::get_mut(storage);
},
_ => quote! {},
};
let before_method_call = match before_kind {
BeforeKind::WithBefore | BeforeKind::OnlyBefore => {
let before_method = format_ident!("__before_{method_name}", span = method_name.span());
if let ReceiverType::GdSelf = signature_info.receiver_type {
quote! { ::godot::private::Storage::get_mut(storage).#before_method(); }
} else {
quote! { __gdext_self.#before_method(); }
}
}
BeforeKind::Without => TokenStream::new(),
};
match signature_info.receiver_type {
ReceiverType::Ref | ReceiverType::Mut => {
let method_call;
let sig_tuple_annotation;
if matches!(before_kind, BeforeKind::OnlyBefore) {
sig_tuple_annotation = TokenStream::new();
method_call = TokenStream::new()
} else if let Some(interface_trait) = interface_trait {
let instance_ref = match signature_info.receiver_type {
ReceiverType::Ref => quote! { &__gdext_self },
ReceiverType::Mut => quote! { &mut __gdext_self },
_ => unreachable!("unexpected receiver type"), };
sig_tuple_annotation = make_sig_tuple_annotation(trait_base_class, method_name);
method_call = quote! {
<#class_name as #interface_trait>::#method_name( #instance_ref, #(#params),* )
};
} else {
sig_tuple_annotation = TokenStream::new();
method_call = quote! {
__gdext_self.#method_name( #(#params),* )
};
};
quote! {
|instance_ptr, #param_ident| {
let #params_tuple #sig_tuple_annotation = #param_ident;
let storage =
unsafe { ::godot::private::as_storage::<#class_name>(instance_ptr) };
#instance_decl
#before_method_call
#method_call
}
}
}
ReceiverType::GdSelf => {
let sig_tuple_annotation = if interface_trait.is_some() {
make_sig_tuple_annotation(trait_base_class, method_name)
} else {
TokenStream::new()
};
quote! {
|instance_ptr, #param_ident| {
let #params_tuple #sig_tuple_annotation = #param_ident;
let storage =
unsafe { ::godot::private::as_storage::<#class_name>(instance_ptr) };
#before_method_call
#class_name::#method_name(::godot::private::Storage::get_gd(storage), #(#params),*)
}
}
}
ReceiverType::Static => {
quote! {
|_, #param_ident| {
let #params_tuple = #param_ident;
#class_name::#method_name(#(#params),*)
}
}
}
}
}
fn map_self_to_class_name<In, Out>(tokens: In, class_name: &Ident) -> Out
where
In: IntoIterator<Item = TokenTree>,
Out: FromIterator<TokenTree>,
{
tokens
.into_iter()
.map(|tt| match tt {
TokenTree::Ident(ident) if ident == "Self" => TokenTree::Ident(class_name.clone()),
TokenTree::Group(group) => TokenTree::Group(Group::new(
group.delimiter(),
map_self_to_class_name(group.stream(), class_name),
)),
tt => tt,
})
.collect()
}
pub(crate) fn into_signature_info(
signature: venial::Function,
class_name: &Ident,
has_gd_self: bool,
) -> SignatureInfo {
let method_name = signature.name.clone();
let mut receiver_type = if has_gd_self {
ReceiverType::GdSelf
} else {
ReceiverType::Static
};
let num_params = signature.params.inner.len();
let params_span = signature.span();
let mut param_idents = Vec::with_capacity(num_params);
let mut param_types = Vec::with_capacity(num_params);
let return_type = match signature.return_ty {
None => quote! { () },
Some(ty) => map_self_to_class_name(ty.tokens, class_name),
};
let mut next_unnamed_index = 0;
let mut modified_param_types = vec![];
for (index, (arg, _)) in signature.params.inner.into_iter().enumerate() {
match arg {
venial::FnParam::Receiver(recv) => {
assert_ne!(receiver_type, ReceiverType::GdSelf);
assert!(recv.tk_ref.is_some());
receiver_type = if recv.tk_mut.is_some() {
ReceiverType::Mut
} else {
ReceiverType::Ref
};
}
venial::FnParam::Typed(arg) => {
let index = if receiver_type == ReceiverType::GdSelf {
index + 1
} else {
index
};
let ident = maybe_rename_parameter(arg.name, &mut next_unnamed_index);
let ty = match maybe_change_parameter_type(arg.ty, &method_name, index) {
Ok(ty) => {
modified_param_types.push((index, ty.clone()));
ty
}
Err(ty) => venial::TypeExpr {
tokens: map_self_to_class_name(ty.tokens, class_name),
},
};
param_types.push(ty);
param_idents.push(ident);
}
}
}
SignatureInfo {
method_name,
receiver_type,
params_span,
param_idents,
param_types,
return_type,
modified_param_types,
optional_param_default_exprs: vec![], }
}
fn maybe_change_parameter_type(
param_ty: venial::TypeExpr,
method_name: &Ident,
param_index: usize,
) -> Result<venial::TypeExpr, venial::TypeExpr> {
if param_index == 1
&& (method_name == "process" || method_name == "physics_process")
&& param_ty.tokens.len() == 1
&& param_ty.tokens[0].to_string() == "f32"
{
let f64_ty = Ident::new("f64", param_ty.span());
Ok(venial::TypeExpr {
tokens: vec![TokenTree::Ident(f64_ty)],
})
} else {
Err(param_ty)
}
}
pub(crate) fn maybe_rename_parameter(param_ident: Ident, next_unnamed_index: &mut i32) -> Ident {
let param_str = param_ident.to_string();
if param_str == "_" {
let ident = format_ident!("__unnamed_{next_unnamed_index}", span = param_ident.span());
*next_unnamed_index += 1;
ident
} else if let Some(remain) = param_str.strip_prefix('_') {
safe_ident(remain)
} else {
param_ident
}
}
fn make_method_flags(
method_type: ReceiverType,
is_script_virtual: bool,
) -> Result<TokenStream, String> {
let flags = quote! { ::godot::register::info::MethodFlags };
let base_flags = match method_type {
ReceiverType::Ref => {
quote! { #flags::NORMAL | #flags::CONST }
}
ReceiverType::Mut | ReceiverType::GdSelf => {
quote! { #flags::NORMAL }
}
ReceiverType::Static => {
if is_script_virtual {
return Err(
"#[func(virtual)] is not allowed for associated (static) functions".to_string(),
);
}
quote! { #flags::NORMAL | #flags::STATIC }
}
};
let flags = if is_script_virtual {
quote! { #base_flags | #flags::VIRTUAL }
} else {
base_flags
};
Ok(flags)
}
fn make_varcall_fn(
call_ctx: &TokenStream,
wrapped_method: &TokenStream,
default_parameters: &TokenStream,
) -> TokenStream {
let invocation = make_varcall_invocation(wrapped_method, default_parameters);
quote! {
unsafe extern "C" fn varcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: sys::GDExtensionInt,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
) {
let call_ctx = #call_ctx;
::godot::private::handle_fallible_varcall(
&call_ctx,
&mut *err,
|| #invocation
);
}
}
}
fn make_ptrcall_fn(call_ctx: &TokenStream, wrapped_method: &TokenStream) -> TokenStream {
let invocation = make_ptrcall_invocation(wrapped_method, false);
quote! {
unsafe extern "C" fn ptrcall_fn(
_method_data: *mut std::ffi::c_void,
instance_ptr: sys::GDExtensionClassInstancePtr,
args_ptr: *const sys::GDExtensionConstTypePtr,
ret: sys::GDExtensionTypePtr,
) {
let call_ctx = #call_ctx;
::godot::private::handle_fallible_ptrcall(
&call_ctx,
|| #invocation
);
}
}
}
fn make_ptrcall_invocation(wrapped_method: &TokenStream, is_virtual: bool) -> TokenStream {
let ptrcall_type = if is_virtual {
quote! { sys::PtrcallType::Virtual }
} else {
quote! { sys::PtrcallType::Standard }
};
quote! {
::godot::private::Signature::<CallParams, CallRet>::in_ptrcall(
instance_ptr,
&call_ctx,
args_ptr,
ret,
#wrapped_method,
#ptrcall_type,
)
}
}
fn make_varcall_invocation(
wrapped_method: &TokenStream,
default_parameters: &TokenStream,
) -> TokenStream {
quote! {
{
let defaults = #default_parameters;
::godot::private::Signature::<CallParams, CallRet>::in_varcall(
instance_ptr,
&call_ctx,
args_ptr,
arg_count,
&defaults,
ret,
err,
#wrapped_method,
)
}
}
}
fn make_call_context(class_name_str: &str, method_name_str: &str) -> TokenStream {
quote! {
::godot::private::CallContext::func(#class_name_str, #method_name_str)
}
}
fn make_sig_tuple_annotation(trait_base_class: &Ident, method_name: &Ident) -> TokenStream {
let span = method_name.span();
let rust_sig_name = format_ident!("Sig_{method_name}", span = span);
quote_spanned! { span=>
: ::godot::private::virtuals::#trait_base_class::#rust_sig_name
}
}
pub fn bail_attr<R>(attr_name: &Ident, msg: &str, method_name: &Ident) -> ParseResult<R> {
bail!(method_name, "#[{attr_name}]: {msg}")
}
pub fn validate_receiver_extract_gdself(
signature: &mut venial::Function,
has_gd_self: bool,
attr_name: &Ident,
) -> ParseResult<Option<Ident>> {
let param_ident = if has_gd_self {
let ident = extract_gd_self(signature, attr_name)?;
Some(ident)
} else {
validate_ref_receiver(signature)?;
None
};
Ok(param_ident)
}
fn validate_ref_receiver(signature: &venial::Function) -> ParseResult<()> {
if let Some((venial::FnParam::Receiver(recv), _)) = signature.params.first()
&& recv.tk_ref.is_none()
{
return bail!(
&recv.tk_self,
"#[func] does not support `self` receiver (by-value); use `&self` or `&mut self`"
);
}
Ok(())
}
fn extract_gd_self(signature: &mut venial::Function, attr_name: &Ident) -> ParseResult<Ident> {
if signature.params.is_empty() {
return bail_attr(
attr_name,
"with attribute key `gd_self`, the method must have a first parameter of type Gd<Self>",
&signature.name,
);
}
let param = signature.params.inner.remove(0);
let venial::FnParam::Typed(param) = param.0 else {
return bail_attr(
attr_name,
"with attribute key `gd_self`, the first parameter must be Gd<Self> (not a `self` receiver)",
&signature.name,
);
};
Ok(param.name)
}