use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
use quote::spanned::Spanned;
use quote::{ToTokens, format_ident, quote};
use crate::class::data_models::func;
use crate::class::{
ConstDefinition, FuncDefinition, RpcAttr, RpcMode, SignalDefinition, SignatureInfo,
TransferMode, into_signature_info, make_constant_registration, make_method_registration,
make_signal_registrations,
};
use crate::util::{
KvParser, bail, c_str, format_funcs_collection_struct, ident, make_funcs_collection_constants,
replace_class_in_path, require_api_version,
};
use crate::{ParseResult, handle_mutually_exclusive_keys, util};
enum ItemAttrType {
Func(FuncAttr, Option<RpcAttr>),
Signal(SignalAttr, venial::AttributeValue),
Const(#[allow(dead_code)] venial::AttributeValue),
}
struct ItemAttr {
attr_name: Ident,
ty: ItemAttrType,
}
enum AttrParseResult {
Func(FuncAttr),
Rpc(RpcAttr),
FuncRpc(FuncAttr, RpcAttr),
Signal(SignalAttr, venial::AttributeValue),
Constant(#[allow(dead_code)] venial::AttributeValue),
}
impl AttrParseResult {
fn into_attr_ty(self) -> ItemAttrType {
match self {
AttrParseResult::Func(func) => ItemAttrType::Func(func, None),
AttrParseResult::Rpc(rpc) => ItemAttrType::Func(FuncAttr::default(), Some(rpc)),
AttrParseResult::FuncRpc(func, rpc) => ItemAttrType::Func(func, Some(rpc)),
AttrParseResult::Signal(signal, attr_val) => ItemAttrType::Signal(signal, attr_val),
AttrParseResult::Constant(constant) => ItemAttrType::Const(constant),
}
}
}
#[derive(Default)]
struct FuncAttr {
pub rename: Option<String>,
pub is_virtual: bool,
pub has_gd_self: bool,
}
#[derive(Default)]
struct SignalAttr {
pub no_builder: bool,
pub internal: bool,
}
pub(crate) struct InherentImplAttr {
pub secondary: bool,
pub no_typed_signals: bool,
}
pub fn transform_inherent_impl(
meta: InherentImplAttr,
mut impl_block: venial::Impl,
self_path: venial::Path,
) -> ParseResult<TokenStream> {
let class_name = util::validate_impl(&impl_block, None, "godot_api")?;
let class_name_obj = util::class_name_obj(&class_name);
let prv = quote! { ::godot::private };
let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?;
let consts = process_godot_constants(&mut impl_block)?;
let inherent_impl_docs =
crate::docs::make_trait_docs_registration(&funcs, &consts, &signals, &class_name, &prv);
let funcs_collection_impl = if meta.secondary {
TokenStream::new()
} else {
let func_consts = make_funcs_collection_constants(&funcs, &class_name);
if func_consts.is_empty() {
TokenStream::new()
} else {
let struct_name = format_funcs_collection_struct(&class_name);
let funcs_path = replace_class_in_path(self_path, struct_name);
quote! {
impl #funcs_path {
#(#func_consts)*
}
}
}
};
let (signal_registrations, signal_symbol_types) = make_signal_registrations(
&signals,
&class_name,
&class_name_obj,
meta.no_typed_signals,
)?;
#[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
let rpc_registrations = crate::class::make_rpc_registrations_fn(&class_name, &funcs);
#[cfg(not(feature = "codegen-full"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-full"))))]
let rpc_registrations = TokenStream::new();
let method_registrations: Vec<TokenStream> = funcs
.into_iter()
.map(|func_def| make_method_registration(&class_name, func_def, None))
.collect::<ParseResult<Vec<TokenStream>>>()?;
let constant_registration = make_constant_registration(consts, &class_name, &class_name_obj)?;
let fill_storage = {
quote! {
::godot::sys::plugin_execute_pre_main!({
let mut guard = #class_name::__registration_storage().lock().unwrap();
guard.0.push(|| {
#( #method_registrations )*
#( #signal_registrations )*
});
guard.1.push(|| {
#constant_registration
});
});
}
};
if !meta.secondary {
let storage = quote! {
impl #class_name {
#[doc(hidden)]
pub fn __registration_storage() -> &'static std::sync::Mutex<(Vec<fn()>, Vec<fn()>)> {
static STORAGE: std::sync::Mutex<(Vec<fn()>, Vec<fn()>)>
= std::sync::Mutex::new((Vec::new(), Vec::new()));
&STORAGE
}
}
};
let trait_impl = quote! {
impl ::godot::obj::cap::ImplementsGodotApi for #class_name {
fn __register_methods() {
let guard = #class_name::__registration_storage().lock().unwrap();
for f in guard.0.iter() {
f();
}
}
fn __register_constants() {
let guard = #class_name::__registration_storage().lock().unwrap();
for f in guard.1.iter() {
f();
}
}
#rpc_registrations
}
};
let class_registration = quote! {
::godot::sys::plugin_add!(#prv::__GODOT_PLUGIN_REGISTRY; #prv::ClassPlugin::new::<#class_name>(
#prv::PluginItem::InherentImpl(#prv::InherentImpl::new::<#class_name>())
));
};
let result = quote! {
#impl_block
#funcs_collection_impl
#storage
#trait_impl
#fill_storage
#class_registration
#signal_symbol_types
#inherent_impl_docs
};
Ok(result)
} else {
let result = quote! {
#impl_block
#funcs_collection_impl
#fill_storage
#inherent_impl_docs
};
Ok(result)
}
}
fn process_godot_fns(
class_name: &Ident,
impl_block: &mut venial::Impl,
is_secondary_impl: bool,
) -> ParseResult<(Vec<FuncDefinition>, Vec<SignalDefinition>)> {
let mut func_definitions = vec![];
let mut signal_definitions = vec![];
let mut virtual_functions = vec![];
let mut removed_indexes = vec![];
for (index, item) in impl_block.body_items.iter_mut().enumerate() {
let venial::ImplMember::AssocFunction(function) = item else {
continue;
};
let Some(attr) = parse_attributes(function)? else {
continue;
};
if function.qualifiers.tk_default.is_some()
|| function.qualifiers.tk_const.is_some()
|| function.qualifiers.tk_async.is_some()
|| function.qualifiers.tk_unsafe.is_some()
|| function.qualifiers.tk_extern.is_some()
|| function.qualifiers.extern_abi.is_some()
{
return bail!(
&function.qualifiers,
"#[func]: fn qualifiers are not allowed"
);
}
if function.generic_params.is_some() {
return bail!(
&function.generic_params,
"#[func]: generic fn parameters are not supported"
);
}
validate_no_references(function)?;
match attr.ty {
ItemAttrType::Func(func, rpc_info) => {
if rpc_info.is_some() && is_secondary_impl {
return bail!(
&function,
"#[rpc] is currently not supported in secondary impl blocks",
)?;
}
let external_attributes = function.attributes.clone();
let mut signature = util::reduce_to_signature(function);
let gd_self_parameter = func::validate_receiver_extract_gdself(
&mut signature,
func.has_gd_self,
&attr.attr_name,
)?;
let mut signature_info =
into_signature_info(signature.clone(), class_name, gd_self_parameter.is_some());
let all_param_maybe_defaults = parse_default_expressions(&mut function.params)?;
signature_info.optional_param_default_exprs =
validate_default_exprs(all_param_maybe_defaults, &signature_info.param_idents)?;
let registered_name = if func.is_virtual {
let registered_name = add_virtual_script_call(
&mut virtual_functions,
function,
&signature_info,
class_name,
&func.rename,
gd_self_parameter,
);
Some(registered_name)
} else {
func.rename
};
func_definitions.push(FuncDefinition {
signature_info,
external_attributes,
registered_name,
is_script_virtual: func.is_virtual,
rpc_info,
is_generated_accessor: false,
});
}
ItemAttrType::Signal(ref signal, ref _attr_val) => {
if is_secondary_impl {
return bail!(
function,
"#[signal] is currently not supported in secondary impl blocks",
);
}
if function.return_ty.is_some() {
return bail!(
&function.return_ty,
"#[signal] does not support return types"
);
}
if function.body.is_some() {
return bail!(
&function.body,
"#[signal] must not have a body; declare the function with a semicolon"
);
}
let external_attributes = function.attributes.clone();
let mut fn_signature = util::reduce_to_signature(function);
fn_signature.vis_marker = function.vis_marker.clone();
signal_definitions.push(SignalDefinition {
fn_signature,
external_attributes,
has_builder: !signal.no_builder,
is_internal: signal.internal,
});
removed_indexes.push(index);
}
ItemAttrType::Const(_) => {
return bail!(
function,
"#[constant] can only be used on associated constant",
);
}
}
}
for index in removed_indexes.into_iter().rev() {
impl_block.body_items.remove(index);
}
for f in virtual_functions.into_iter() {
let member = venial::ImplMember::AssocFunction(f);
impl_block.body_items.push(member);
}
Ok((func_definitions, signal_definitions))
}
fn process_godot_constants(decl: &mut venial::Impl) -> ParseResult<Vec<ConstDefinition>> {
let mut constant_signatures = vec![];
for item in decl.body_items.iter_mut() {
let venial::ImplMember::AssocConstant(constant) = item else {
continue;
};
if let Some(attr) = parse_attributes(constant)? {
match attr.ty {
ItemAttrType::Func(_, _) => {
return bail!(constant, "#[func] and #[rpc] can only be used on functions");
}
ItemAttrType::Signal(_, _) => {
return bail!(constant, "#[signal] can only be used on functions");
}
ItemAttrType::Const(_) => {
if constant.initializer.is_none() {
return bail!(constant, "exported constant must have initializer");
}
let definition = ConstDefinition {
raw_constant: constant.clone(),
};
constant_signatures.push(definition);
}
}
}
}
Ok(constant_signatures)
}
fn add_virtual_script_call(
virtual_functions: &mut Vec<venial::Function>,
function: &mut venial::Function,
signature_info: &SignatureInfo,
class_name: &Ident,
rename: &Option<String>,
gd_self_parameter: Option<Ident>,
) -> String {
#[allow(clippy::assertions_on_constants)]
{
assert!(cfg!(since_api = "4.3"));
}
let is_params = function.params.iter_mut().skip(1); let should_param_names = signature_info.param_idents.iter();
is_params
.zip(should_param_names)
.for_each(|(param, should_param_name)| {
if let venial::FnParam::Typed(param) = &mut param.0 {
param.name = should_param_name.clone();
}
});
let class_name_str = class_name.to_string();
let early_bound_name = format_ident!(
"__earlybound_{}",
function.name,
span = function.name.span()
);
let method_name_str = match rename {
Some(rename) => rename.clone(),
None => format!("_{}", function.name),
};
let method_name_cstr = c_str(&method_name_str);
let call_params = signature_info.params_type();
let call_ret = &signature_info.return_type;
let arg_names = &signature_info.param_idents;
let (object_ptr, receiver);
if let Some(gd_self_parameter) = gd_self_parameter {
object_ptr = quote! { #gd_self_parameter.obj_sys() };
receiver = gd_self_parameter;
} else {
object_ptr = quote! { <Self as ::godot::obj::WithBaseField>::base_field(self).obj_sys() };
receiver = ident("self");
};
let code = quote! {
let object_ptr = #object_ptr;
let method_sname = ::godot::builtin::StringName::__cstr(#method_name_cstr);
let method_sname_ptr = method_sname.string_sys();
let has_virtual_override = unsafe { ::godot::private::has_virtual_script_method(object_ptr, method_sname_ptr) };
if has_virtual_override {
type CallParams = #call_params;
type CallRet = #call_ret;
let args = (#( #arg_names, )*);
unsafe {
::godot::private::Signature::<CallParams, CallRet>::out_script_virtual_call(
#class_name_str,
#method_name_str,
method_sname_ptr,
object_ptr,
args,
)
}
} else {
Self::#early_bound_name(#receiver, #( #arg_names ),*)
}
};
let mut early_bound_function = venial::Function {
name: early_bound_name,
body: Some(Group::new(Delimiter::Brace, code)),
..function.clone()
};
std::mem::swap(&mut function.body, &mut early_bound_function.body);
virtual_functions.push(early_bound_function);
method_name_str
}
fn validate_no_references(function: &venial::Function) -> ParseResult<()> {
for (param, _) in function.params.inner.iter() {
if let venial::FnParam::Typed(arg) = param
&& let Some(TokenTree::Punct(p)) = arg.ty.tokens.first()
&& p.as_char() == '&'
{
return bail!(
&arg.ty,
"#[func] does not support reference parameters \
(`&T` or `&mut T`); use a value type instead"
);
}
}
if let Some(ref ret_ty) = function.return_ty
&& let Some(TokenTree::Punct(p)) = ret_ty.tokens.first()
&& p.as_char() == '&'
{
return bail!(
ret_ty,
"#[func] does not support reference return types \
(`&T` or `&mut T`); use a value type instead"
);
}
Ok(())
}
fn parse_attributes<T: ImplItem>(item: &mut T) -> ParseResult<Option<ItemAttr>> {
let span = util::span_of(item);
parse_attributes_inner(item.attributes_mut(), span)
}
fn parse_attributes_inner(
attributes: &mut Vec<venial::Attribute>,
full_item_span: Span,
) -> ParseResult<Option<ItemAttr>> {
let mut found = None;
let mut index = 0;
while let Some(attr) = attributes.get(index) {
index += 1;
let Some(attr_name) = attr.get_single_path_segment() else {
continue;
};
let parsed_attr = match attr_name {
name if name == "func" => parse_func_attr(attributes)?,
name if name == "rpc" => parse_rpc_attr(attributes)?,
name if name == "signal" => parse_signal_attr(attributes, attr)?,
name if name == "constant" => parse_constant_attr(attributes, attr)?,
_ => continue,
};
let attr_name = attr_name.clone();
attributes.remove(index - 1); index -= 1;
let (new_name, new_attr) = match (found, parsed_attr) {
(None, parsed) => (attr_name, parsed),
(Some((found_name, AttrParseResult::Func(func))), AttrParseResult::Rpc(rpc))
| (Some((found_name, AttrParseResult::Rpc(rpc))), AttrParseResult::Func(func)) => (
ident(&format!("{found_name}_{attr_name}")),
AttrParseResult::FuncRpc(func, rpc),
),
(Some((found_name, _)), _) => {
return bail!(
full_item_span,
"attributes `{found_name}` and `{attr_name}` cannot be used in the same declaration"
);
}
};
found = Some((new_name, new_attr));
}
Ok(found.map(|(attr_name, attr)| ItemAttr {
attr_name,
ty: attr.into_attr_ty(),
}))
}
fn parse_func_attr(attributes: &[venial::Attribute]) -> ParseResult<AttrParseResult> {
let mut parser = KvParser::parse(attributes, "func")?.unwrap();
let rename = parser.handle_expr("rename")?.map(|ts| ts.to_string());
let is_virtual = if let Some(span) = parser.handle_alone_with_span("virtual")? {
require_api_version!("4.3", span, "#[func(virtual)]")?;
true
} else {
false
};
let has_gd_self = parser.handle_alone("gd_self")?;
parser.finish()?;
Ok(AttrParseResult::Func(FuncAttr {
rename,
is_virtual,
has_gd_self,
}))
}
fn parse_rpc_attr(attributes: &[venial::Attribute]) -> ParseResult<AttrParseResult> {
let mut parser = KvParser::parse(attributes, "rpc")?.unwrap();
let rpc_mode =
handle_mutually_exclusive_keys(&mut parser, "#[rpc]", &["any_peer", "authority"])?
.map(|idx| RpcMode::from_usize(idx).unwrap());
let transfer_mode = handle_mutually_exclusive_keys(
&mut parser,
"#[rpc]",
&["reliable", "unreliable", "unreliable_ordered"],
)?
.map(|idx| TransferMode::from_usize(idx).unwrap());
let call_local =
handle_mutually_exclusive_keys(&mut parser, "#[rpc]", &["call_local", "call_remote"])?
.map(|idx| idx == 0);
let channel = parser.handle_usize("channel")?.map(|x| x as u32);
let config_expr = parser.handle_expr("config")?;
let item_span = parser.span();
parser.finish()?;
let rpc_attr = match (
config_expr,
(&rpc_mode, &transfer_mode, &call_local, &channel),
) {
(Some(expr), (None, None, None, None)) => RpcAttr::Expression(expr),
(Some(_), _) => {
return bail!(
item_span,
"`#[rpc(config = ...)]` is mutually exclusive with any other parameters(`any_peer`, `reliable`, `call_local`, `channel = 0`)"
);
}
_ => RpcAttr::SeparatedArgs {
rpc_mode,
transfer_mode,
call_local,
channel,
},
};
Ok(AttrParseResult::Rpc(rpc_attr))
}
fn parse_signal_attr(
attributes: &[venial::Attribute],
attr: &venial::Attribute,
) -> ParseResult<AttrParseResult> {
let mut parser = KvParser::parse(attributes, "signal")?.unwrap();
let no_builder = parser.handle_alone("__no_builder")?;
let internal = parser.handle_alone("internal")?;
parser.finish()?;
let signal_attr = SignalAttr {
no_builder,
internal,
};
Ok(AttrParseResult::Signal(signal_attr, attr.value.clone()))
}
fn parse_constant_attr(
attributes: &[venial::Attribute],
attr: &venial::Attribute,
) -> ParseResult<AttrParseResult> {
let parser = KvParser::parse(attributes, "constant")?.unwrap();
parser.finish()?;
Ok(AttrParseResult::Constant(attr.value.clone()))
}
fn parse_default_expressions(
params: &mut venial::Punctuated<venial::FnParam>,
) -> ParseResult<Vec<Option<TokenStream>>> {
let mut res = vec![];
for param in params.iter_mut() {
let typed_param = match &mut param.0 {
venial::FnParam::Receiver(_) => continue,
venial::FnParam::Typed(fn_typed_param) => fn_typed_param,
};
let optional_value = match KvParser::parse_remove(&mut typed_param.attributes, "opt")? {
None => None,
Some(mut parser) => Some(parser.handle_expr_required("default")?),
};
res.push(optional_value);
}
Ok(res)
}
fn validate_default_exprs(
all_param_maybe_defaults: Vec<Option<TokenStream>>,
param_idents: &[Ident],
) -> ParseResult<Vec<TokenStream>> {
let mut must_be_default = false;
let mut result = Vec::new();
for (i, param) in all_param_maybe_defaults.into_iter().enumerate() {
match (param, must_be_default) {
(Some(default_expr), false) => {
must_be_default = true;
result.push(default_expr);
}
(Some(default_expr), true) => {
result.push(default_expr);
}
(None, false) => {}
(None, true) => {
let name = ¶m_idents[i];
return bail!(
name,
"parameter `{name}` must have a default value, because previous parameters are already optional",
);
}
}
}
Ok(result)
}
trait ImplItem
where
Self: ToTokens,
for<'a> &'a Self: Spanned,
{
fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute>;
}
impl ImplItem for venial::Function {
fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute> {
&mut self.attributes
}
}
impl ImplItem for venial::Constant {
fn attributes_mut(&mut self) -> &mut Vec<venial::Attribute> {
&mut self.attributes
}
}