Skip to content

Commit

Permalink
#[var(get=f, set=f)]: properly handle #[func]s that have been rename…
Browse files Browse the repository at this point in the history
…d with (rename=godot_name)
  • Loading branch information
0x53A committed Jan 27, 2025
1 parent bf4ddd9 commit 74fab79
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 12 deletions.
7 changes: 7 additions & 0 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::class::{
into_signature_info, make_existence_check, make_method_registration, Field, FieldHint,
FuncDefinition,
};
use crate::util::make_function_registered_name_constant;
use crate::util::KvParser;
use crate::{util, ParseResult};

Expand Down Expand Up @@ -166,6 +167,7 @@ pub struct GetterSetterImpl {
pub function_name: Ident,
pub function_impl: TokenStream,
pub export_token: TokenStream,
pub func_registered_name_const: TokenStream,
}

impl GetterSetterImpl {
Expand Down Expand Up @@ -206,6 +208,9 @@ impl GetterSetterImpl {
}
};

let func_registered_name_const =
make_function_registered_name_constant(class_name, &function_name, None, &[]);

let signature = util::parse_signature(signature);
let export_token = make_method_registration(
class_name,
Expand All @@ -229,6 +234,7 @@ impl GetterSetterImpl {
function_name,
function_impl,
export_token,
func_registered_name_const,
}
}

Expand All @@ -237,6 +243,7 @@ impl GetterSetterImpl {
function_name: function_name.clone(),
function_impl: TokenStream::new(),
export_token: make_existence_check(function_name),
func_registered_name_const: TokenStream::new(),
}
}
}
Expand Down
25 changes: 24 additions & 1 deletion godot-macros/src/class/data_models/inherent_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ use crate::class::{
make_signal_registrations, ConstDefinition, FuncDefinition, RpcAttr, RpcMode, SignalDefinition,
SignatureInfo, TransferMode,
};
use crate::util::{bail, c_str, ident, require_api_version, KvParser};
use crate::util::{
bail, c_str, error_fn, format_function_registered_name_struct_name, ident,
make_function_registered_name_constants, replace_class_in_path, require_api_version, KvParser,
};
use crate::{handle_mutually_exclusive_keys, util, ParseResult};

use proc_macro2::{Delimiter, Group, Ident, TokenStream};
use quote::spanned::Spanned;
use quote::{format_ident, quote};
use venial::Path;

/// Attribute for user-declared function.
enum ItemAttrType {
Expand Down Expand Up @@ -89,6 +93,19 @@ pub fn transform_inherent_impl(
#[cfg(not(all(feature = "register-docs", since_api = "4.3")))]
let docs = quote! {};

// This is the container struct that holds the names of all registered #[func]s.
// (The struct is declared by the macro derive_godot_class.)
let class_functions_name = format_function_registered_name_struct_name(&class_name);
// As the impl block could be of the form "path::class", and we add a second impl block below, we need the full path, not just the class name.
let this_class_full_path = impl_block.self_ty.as_path().ok_or(error_fn(
"unexpected: the function already checked 'as_path' above in validate_impl",
&impl_block,
))?;
let class_functions_path: Path =
replace_class_in_path(this_class_full_path, class_functions_name);
// For each #[func] in this impl block, we create one constant.
let func_name_constants = make_function_registered_name_constants(&funcs, &class_name);

let signal_registrations = make_signal_registrations(signals, &class_name_obj);

#[cfg(feature = "codegen-full")]
Expand Down Expand Up @@ -164,6 +181,9 @@ pub fn transform_inherent_impl(
#trait_impl
#fill_storage
#class_registration
impl #class_functions_path {
#( #func_name_constants )*
}
};

Ok(result)
Expand All @@ -174,6 +194,9 @@ pub fn transform_inherent_impl(
let result = quote! {
#impl_block
#fill_storage
impl #class_functions_path {
#( #func_name_constants )*
}
};

Ok(result)
Expand Down
43 changes: 36 additions & 7 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
//! Parsing the `var` and `export` attributes on fields.
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
use crate::util::{
format_function_registered_name_constant_name, format_function_registered_name_struct_name,
ident,
};
use proc_macro2::{Ident, TokenStream};
use quote::quote;

Expand Down Expand Up @@ -38,6 +42,7 @@ impl FieldHint {

pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
let mut getter_setter_impls = Vec::new();
let mut func_registered_name_consts = Vec::new();
let mut export_tokens = Vec::new();

for field in &fields.all_fields {
Expand Down Expand Up @@ -134,33 +139,47 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
},
};

let getter_name = make_getter_setter(
// Note: (getter/setter)_tokens can be either a path ``Class_Functions::constant_name`` or an empty string ``""``.

let getter_tokens = make_getter_setter(
getter.to_impl(class_name, GetSet::Get, field),
&mut getter_setter_impls,
&mut func_registered_name_consts,
&mut export_tokens,
class_name,
);
let setter_name = make_getter_setter(
let setter_tokens = make_getter_setter(
setter.to_impl(class_name, GetSet::Set, field),
&mut getter_setter_impls,
&mut func_registered_name_consts,
&mut export_tokens,
class_name,
);

export_tokens.push(quote! {
::godot::register::private::#registration_fn::<#class_name, #field_type>(
#field_name,
#getter_name,
#setter_name,
#getter_tokens,
#setter_tokens,
#hint,
#usage_flags,
);
});
}

// For each generated #[func], add a const.
// This is the name of the container struct, which is declared by the derive macro GodotClass.
let class_functions_name = format_function_registered_name_struct_name(class_name);

quote! {
impl #class_name {
#(#getter_setter_impls)*
}

impl #class_functions_name {
#(#func_registered_name_consts)*
}

impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
fn __register_exports() {
#(
Expand All @@ -176,20 +195,30 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
fn make_getter_setter(
getter_setter_impl: Option<GetterSetterImpl>,
getter_setter_impls: &mut Vec<TokenStream>,
func_registered_name_consts: &mut Vec<TokenStream>,
export_tokens: &mut Vec<TokenStream>,
) -> String {
class_name: &Ident,
) -> TokenStream {
if let Some(getter_impl) = getter_setter_impl {
let GetterSetterImpl {
function_name,
function_impl,
export_token,
func_registered_name_const,
} = getter_impl;

getter_setter_impls.push(function_impl);
func_registered_name_consts.push(func_registered_name_const);
export_tokens.push(export_token);

function_name.to_string()
let getter_setter_name = function_name.to_string();

let class_functions_name = format_function_registered_name_struct_name(class_name);

let getter_setter_fn_const =
format_function_registered_name_constant_name(class_name, &ident(&getter_setter_name));
quote! { #class_functions_name::#getter_setter_fn_const }
} else {
String::new()
quote! { "" }
}
}
13 changes: 12 additions & 1 deletion godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::class::{
make_property_impl, make_virtual_callback, BeforeKind, Field, FieldDefault, FieldExport,
FieldVar, Fields, SignatureInfo,
};
use crate::util::{bail, error, ident, path_ends_with_complex, require_api_version, KvParser};
use crate::util::{
bail, error, format_function_registered_name_struct_name, ident, path_ends_with_complex,
require_api_version, KvParser,
};
use crate::{handle_mutually_exclusive_keys, util, ParseResult};

pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
Expand Down Expand Up @@ -134,6 +137,13 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
modifiers.push(quote! { with_tool })
}

// Declares a dummy struct that, for each #[func], holds a constant that maps the rust name to the name under which it is registered in godot.
let class_functions_name = format_function_registered_name_struct_name(class_name);
let class_functions_struct = quote! {
#[doc(hidden)]
pub struct #class_functions_name { }
};

Ok(quote! {
impl ::godot::obj::GodotClass for #class_name {
type Base = #base_class;
Expand All @@ -157,6 +167,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
type Exportable = <<Self as ::godot::obj::GodotClass>::Base as ::godot::obj::Bounds>::Exportable;
}

#class_functions_struct
#godot_init_impl
#godot_withbase_impl
#godot_exports_impl
Expand Down
113 changes: 110 additions & 3 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

// Note: some code duplication with godot-codegen crate.

use crate::class::FuncDefinition;
use crate::ParseResult;
use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree};
use quote::spanned::Spanned;
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use venial::{Attribute, Path, PathSegment};

mod kv_parser;
mod list_parser;
Expand Down Expand Up @@ -243,8 +245,18 @@ pub(crate) fn extract_cfg_attrs(
attrs: &[venial::Attribute],
) -> impl IntoIterator<Item = &venial::Attribute> {
attrs.iter().filter(|attr| {
attr.get_single_path_segment()
.is_some_and(|name| name == "cfg")
let Some(attr_name) = attr.get_single_path_segment() else {
return false;
};
// #[cfg(condition)]
if attr_name == "cfg" {
return true;
}
// #[cfg_attr(condition, attributes...)], note that there can be multiple attributes seperated by comma.
if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") {
return true;
}
false
})
}

Expand Down Expand Up @@ -303,3 +315,98 @@ pub fn venial_parse_meta(

venial::parse_item(input)
}

// ----------------------------------------------------------------------------------------------------------------------------------------------

// util functions for handling #[func]s and #[var(get=f, set=f)]

pub fn make_function_registered_name_constants(
funcs: &[FuncDefinition],
class_name: &Ident,
) -> Vec<TokenStream> {
funcs
.iter()
.map(|func| {
// The constant needs the same #[cfg] attribute(s) as the function, so that it is only active if the function is also active.
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
.into_iter()
.collect::<Vec<_>>();

make_function_registered_name_constant(
class_name,
&func.signature_info.method_name,
func.registered_name.as_ref(),
&cfg_attributes,
)
})
.collect()
}

/// Funcs can be renamed with `#[func(rename=new_name) fn f();`.
/// To be able to access the renamed function name at a later point, it is saved in a string constant.
pub fn make_function_registered_name_constant(
class_name: &Ident,
func_name: &Ident,
registered_name: Option<&String>,
attributes: &[&Attribute],
) -> TokenStream {
let const_name = format_function_registered_name_constant_name(class_name, func_name);
let const_value = match &registered_name {
Some(renamed) => renamed.to_string(),
None => func_name.to_string(),
};

let doc_comment =
format!("The Rust function `{func_name}` is registered with Godot as `{const_value}`.");

quote! {
#(#attributes)*
#[doc = #doc_comment]
#[doc(hidden)]
#[allow(non_upper_case_globals)]
pub const #const_name: &str = #const_value;
}
}

/// Converts "path::class" to "path::new_class".
pub fn replace_class_in_path(path: Path, new_class: Ident) -> Path {
match path.segments.as_slice() {
// Can't happen, you have at least one segment (the class name).
[] => panic!("unexpected: empty path"),

[_single] => Path {
segments: vec![PathSegment {
ident: new_class,
generic_args: None,
tk_separator_colons: None,
}],
},

[path @ .., _last] => {
let mut segments = vec![];
segments.extend(path.iter().cloned());
segments.push(PathSegment {
ident: new_class,
generic_args: None,
tk_separator_colons: Some([
Punct::new(':', Spacing::Joint),
Punct::new(':', Spacing::Alone),
]),
});
Path { segments }
}
}
}

/// Returns the name of the constant that will be autogenerated.
pub fn format_function_registered_name_constant_name(
_class_name: &Ident,
func_name: &Ident,
) -> Ident {
format_ident!("{func_name}")
}

/// Returns the name of the dummy struct that's used as container for all function name constants.
pub fn format_function_registered_name_struct_name(class_name: &Ident) -> Ident {
format_ident!("__gdext_{class_name}_Functions")
}
Loading

0 comments on commit 74fab79

Please sign in to comment.