Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

in #[var]s, handle renamed #[func]s #1019

Merged
merged 2 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)*
0x53A marked this conversation as resolved.
Show resolved Hide resolved
#[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
Loading