Skip to content

Commit

Permalink
Merge pull request #1019 from 0x53A/dev-handle-func-rename
Browse files Browse the repository at this point in the history
in `#[var]s`, handle renamed `#[func]`s
  • Loading branch information
Bromeon authored Feb 1, 2025
2 parents f50d8e7 + 22127f5 commit 55a7d99
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 27 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_funcs_collection_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 funcs_collection_constant: TokenStream,
}

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

let funcs_collection_constant =
make_funcs_collection_constant(class_name, &function_name, None, &[]);

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

Expand All @@ -238,6 +244,7 @@ impl GetterSetterImpl {
function_name: function_name.clone(),
function_impl: TokenStream::new(),
export_token: make_existence_check(function_name),
funcs_collection_constant: TokenStream::new(),
}
}
}
Expand Down
21 changes: 20 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,7 +10,10 @@ 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, format_funcs_collection_struct, ident, make_funcs_collection_constants,
replace_class_in_path, require_api_version, KvParser,
};
use crate::{handle_mutually_exclusive_keys, util, ParseResult};

use proc_macro2::{Delimiter, Group, Ident, TokenStream};
Expand Down Expand Up @@ -75,6 +78,7 @@ pub struct InherentImplAttr {
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);
Expand All @@ -89,6 +93,15 @@ pub fn transform_inherent_impl(
#[cfg(not(all(feature = "register-docs", since_api = "4.3")))]
let docs = quote! {};

// Container struct holding names of all registered #[func]s.
// The struct is declared by #[derive(GodotClass)].
let funcs_collection = {
let struct_name = format_funcs_collection_struct(&class_name);
replace_class_in_path(self_path, struct_name)
};

// For each #[func] in this impl block, create one constant.
let func_name_constants = make_funcs_collection_constants(&funcs, &class_name);
let signal_registrations = make_signal_registrations(signals, &class_name_obj);

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

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

Ok(result)
Expand Down
58 changes: 38 additions & 20 deletions godot-macros/src/class/data_models/property.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Parsing the `var` and `export` attributes on fields.
//! Parses the `#[var]` and `#[export]` attributes on fields.
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct};
use proc_macro2::{Ident, TokenStream};
use quote::quote;

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

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

for field in &fields.all_fields {
Expand Down Expand Up @@ -134,33 +136,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_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_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 declaration.
// This is the name of the container struct, which is declared by #[derive(GodotClass)].
let class_functions_name = format_funcs_collection_struct(class_name);

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

impl #class_functions_name {
#(#func_name_consts)*
}

impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
fn __register_exports() {
#(
Expand All @@ -176,20 +192,22 @@ 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_name_consts: &mut Vec<TokenStream>,
export_tokens: &mut Vec<TokenStream>,
) -> String {
if let Some(getter_impl) = getter_setter_impl {
let GetterSetterImpl {
function_name,
function_impl,
export_token,
} = getter_impl;

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

function_name.to_string()
} else {
String::new()
}
class_name: &Ident,
) -> TokenStream {
let Some(gs) = getter_setter_impl else {
return quote! { "" };
};

getter_setter_impls.push(gs.function_impl);
func_name_consts.push(gs.funcs_collection_constant);
export_tokens.push(gs.export_token);

// Getters/setters are, like #[func]s, subject to additional code generation: a constant inside a "funcs collection" struct
// stores their Godot name and can be used as an indirection to refer to their true name from other procedural macros.
let funcs_collection = format_funcs_collection_struct(class_name);
let constant = format_funcs_collection_constant(class_name, &gs.function_name);

quote! { #funcs_collection::#constant }
}
14 changes: 13 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_funcs_collection_struct, 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,14 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
modifiers.push(quote! { with_tool })
}

// Declares a "funcs collection" struct that, for holds a constant for each #[func].
// That constant maps the Rust name (constant ident) to the Godot registered name (string value).
let funcs_collection_struct_name = format_funcs_collection_struct(class_name);
let funcs_collection_struct = quote! {
#[doc(hidden)]
pub struct #funcs_collection_struct_name {}
};

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

#funcs_collection_struct
#godot_init_impl
#godot_withbase_impl
#godot_exports_impl
Expand Down
4 changes: 2 additions & 2 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn attribute_godot_api(
)?;
}

if decl.self_ty.as_path().is_none() {
let Some(self_path) = decl.self_ty.as_path() else {
return bail!(decl, "invalid Self type for #[godot_api] impl");
};

Expand All @@ -57,7 +57,7 @@ pub fn attribute_godot_api(
transform_trait_impl(decl)
} else {
match parse_inherent_impl_attr(meta) {
Ok(meta) => transform_inherent_impl(meta, decl),
Ok(meta) => transform_inherent_impl(meta, decl, self_path),
Err(err) => Err(err),
}
}
Expand Down
114 changes: 111 additions & 3 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

// 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};

Expand Down Expand Up @@ -243,8 +244,21 @@ 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...)]. Multiple attributes can be 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 +317,97 @@ pub fn venial_parse_meta(

venial::parse_item(input)
}

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

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

pub fn make_funcs_collection_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_funcs_collection_constant(
class_name,
&func.signature_info.method_name,
func.registered_name.as_ref(),
&cfg_attributes,
)
})
.collect()
}

/// Returns a `const` declaration for the funcs collection struct.
///
/// User-defined functions can be renamed with `#[func(rename=new_name)]`. To be able to access the renamed function name from another macro,
/// a constant is used as indirection.
pub fn make_funcs_collection_constant(
class_name: &Ident,
func_name: &Ident,
registered_name: Option<&String>,
attributes: &[&venial::Attribute],
) -> TokenStream {
let const_name = format_funcs_collection_constant(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: venial::Path, new_class: Ident) -> venial::Path {
match path.segments.as_slice() {
// Can't happen, you have at least one segment (the class name).
[] => unreachable!("empty path"),

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

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

/// Returns the name of the constant inside the func "collection" struct.
pub fn format_funcs_collection_constant(_class_name: &Ident, func_name: &Ident) -> Ident {
format_ident!("{func_name}")
}

/// Returns the name of the struct used as collection for all function name constants.
pub fn format_funcs_collection_struct(class_name: &Ident) -> Ident {
format_ident!("__gdext_{class_name}_Funcs")
}
Loading

0 comments on commit 55a7d99

Please sign in to comment.