Skip to content

Commit 0edf6e3

Browse files
0x53ABromeon
authored andcommitted
#[var(get=f, set=f)]: properly handle #[func]s that have been renamed with (rename=godot_name)
1 parent f50d8e7 commit 0edf6e3

File tree

9 files changed

+329
-12
lines changed

9 files changed

+329
-12
lines changed

godot-macros/src/class/data_models/field_var.rs

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::class::{
1212
into_signature_info, make_existence_check, make_method_registration, Field, FieldHint,
1313
FuncDefinition,
1414
};
15+
use crate::util::make_func_name_constant;
1516
use crate::util::KvParser;
1617
use crate::{util, ParseResult};
1718

@@ -166,6 +167,7 @@ pub struct GetterSetterImpl {
166167
pub function_name: Ident,
167168
pub function_impl: TokenStream,
168169
pub export_token: TokenStream,
170+
pub func_name_const: TokenStream,
169171
}
170172

171173
impl GetterSetterImpl {
@@ -206,6 +208,8 @@ impl GetterSetterImpl {
206208
}
207209
};
208210

211+
let func_name_const = make_func_name_constant(class_name, &function_name, None, &[]);
212+
209213
let signature = util::parse_signature(signature);
210214
let export_token = make_method_registration(
211215
class_name,
@@ -230,6 +234,7 @@ impl GetterSetterImpl {
230234
function_name,
231235
function_impl,
232236
export_token,
237+
func_name_const,
233238
}
234239
}
235240

@@ -238,6 +243,7 @@ impl GetterSetterImpl {
238243
function_name: function_name.clone(),
239244
function_impl: TokenStream::new(),
240245
export_token: make_existence_check(function_name),
246+
func_name_const: TokenStream::new(),
241247
}
242248
}
243249
}

godot-macros/src/class/data_models/inherent_impl.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ use crate::class::{
1010
make_signal_registrations, ConstDefinition, FuncDefinition, RpcAttr, RpcMode, SignalDefinition,
1111
SignatureInfo, TransferMode,
1212
};
13-
use crate::util::{bail, c_str, ident, require_api_version, KvParser};
13+
use crate::util::{
14+
bail, c_str, error_fn, format_func_name_struct_name, ident, make_func_name_constants,
15+
replace_class_in_path, require_api_version, KvParser,
16+
};
1417
use crate::{handle_mutually_exclusive_keys, util, ParseResult};
1518

1619
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
1720
use quote::spanned::Spanned;
1821
use quote::{format_ident, quote};
22+
use venial::Path;
1923

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

96+
// This is the container struct that holds the names of all registered #[func]s.
97+
// (The struct is declared by the macro derive_godot_class.)
98+
let class_functions_name = format_func_name_struct_name(&class_name);
99+
// 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.
100+
let this_class_full_path = impl_block.self_ty.as_path().ok_or(error_fn(
101+
"unexpected: the function already checked 'as_path' above in validate_impl",
102+
&impl_block,
103+
))?;
104+
let class_functions_path: Path =
105+
replace_class_in_path(this_class_full_path, class_functions_name);
106+
// For each #[func] in this impl block, we create one constant.
107+
let func_name_constants = make_func_name_constants(&funcs, &class_name);
108+
92109
let signal_registrations = make_signal_registrations(signals, &class_name_obj);
93110

94111
#[cfg(feature = "codegen-full")]
@@ -164,6 +181,9 @@ pub fn transform_inherent_impl(
164181
#trait_impl
165182
#fill_storage
166183
#class_registration
184+
impl #class_functions_path {
185+
#( #func_name_constants )*
186+
}
167187
};
168188

169189
Ok(result)
@@ -174,6 +194,9 @@ pub fn transform_inherent_impl(
174194
let result = quote! {
175195
#impl_block
176196
#fill_storage
197+
impl #class_functions_path {
198+
#( #func_name_constants )*
199+
}
177200
};
178201

179202
Ok(result)

godot-macros/src/class/data_models/property.rs

+33-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! Parsing the `var` and `export` attributes on fields.
99
1010
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
11+
use crate::util::{format_func_name_constant_name, format_func_name_struct_name, ident};
1112
use proc_macro2::{Ident, TokenStream};
1213
use quote::quote;
1314

@@ -38,6 +39,7 @@ impl FieldHint {
3839

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

4345
for field in &fields.all_fields {
@@ -134,33 +136,47 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
134136
},
135137
};
136138

137-
let getter_name = make_getter_setter(
139+
// Note: (getter/setter)_tokens can be either a path ``Class_Functions::constant_name`` or an empty string ``""``.
140+
141+
let getter_tokens = make_getter_setter(
138142
getter.to_impl(class_name, GetSet::Get, field),
139143
&mut getter_setter_impls,
144+
&mut func_name_consts,
140145
&mut export_tokens,
146+
class_name,
141147
);
142-
let setter_name = make_getter_setter(
148+
let setter_tokens = make_getter_setter(
143149
setter.to_impl(class_name, GetSet::Set, field),
144150
&mut getter_setter_impls,
151+
&mut func_name_consts,
145152
&mut export_tokens,
153+
class_name,
146154
);
147155

148156
export_tokens.push(quote! {
149157
::godot::register::private::#registration_fn::<#class_name, #field_type>(
150158
#field_name,
151-
#getter_name,
152-
#setter_name,
159+
#getter_tokens,
160+
#setter_tokens,
153161
#hint,
154162
#usage_flags,
155163
);
156164
});
157165
}
158166

167+
// For each generated #[func], add a const.
168+
// This is the name of the container struct, which is declared by the derive macro GodotClass.
169+
let class_functions_name = format_func_name_struct_name(class_name);
170+
159171
quote! {
160172
impl #class_name {
161173
#(#getter_setter_impls)*
162174
}
163175

176+
impl #class_functions_name {
177+
#(#func_name_consts)*
178+
}
179+
164180
impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
165181
fn __register_exports() {
166182
#(
@@ -176,20 +192,30 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
176192
fn make_getter_setter(
177193
getter_setter_impl: Option<GetterSetterImpl>,
178194
getter_setter_impls: &mut Vec<TokenStream>,
195+
func_name_consts: &mut Vec<TokenStream>,
179196
export_tokens: &mut Vec<TokenStream>,
180-
) -> String {
197+
class_name: &Ident,
198+
) -> TokenStream {
181199
if let Some(getter_impl) = getter_setter_impl {
182200
let GetterSetterImpl {
183201
function_name,
184202
function_impl,
185203
export_token,
204+
func_name_const,
186205
} = getter_impl;
187206

188207
getter_setter_impls.push(function_impl);
208+
func_name_consts.push(func_name_const);
189209
export_tokens.push(export_token);
190210

191-
function_name.to_string()
211+
let getter_setter_name = function_name.to_string();
212+
213+
let class_functions_name = format_func_name_struct_name(class_name);
214+
215+
let getter_setter_fn_const =
216+
format_func_name_constant_name(class_name, &ident(&getter_setter_name));
217+
quote! { #class_functions_name::#getter_setter_fn_const }
192218
} else {
193-
String::new()
219+
quote! { "" }
194220
}
195221
}

godot-macros/src/class/derive_godot_class.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use crate::class::{
1212
make_property_impl, make_virtual_callback, BeforeKind, Field, FieldDefault, FieldExport,
1313
FieldVar, Fields, SignatureInfo,
1414
};
15-
use crate::util::{bail, error, ident, path_ends_with_complex, require_api_version, KvParser};
15+
use crate::util::{
16+
bail, error, format_func_name_struct_name, ident, path_ends_with_complex, require_api_version,
17+
KvParser,
18+
};
1619
use crate::{handle_mutually_exclusive_keys, util, ParseResult};
1720

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

140+
// 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.
141+
let class_functions_name = format_func_name_struct_name(class_name);
142+
let class_functions_struct = quote! {
143+
#[doc(hidden)]
144+
pub struct #class_functions_name { }
145+
};
146+
137147
Ok(quote! {
138148
impl ::godot::obj::GodotClass for #class_name {
139149
type Base = #base_class;
@@ -157,6 +167,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
157167
type Exportable = <<Self as ::godot::obj::GodotClass>::Base as ::godot::obj::Bounds>::Exportable;
158168
}
159169

170+
#class_functions_struct
160171
#godot_init_impl
161172
#godot_withbase_impl
162173
#godot_exports_impl

godot-macros/src/util/mod.rs

+104-3
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

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

10+
use crate::class::FuncDefinition;
1011
use crate::ParseResult;
11-
use proc_macro2::{Delimiter, Group, Ident, Literal, TokenStream, TokenTree};
12+
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, TokenStream, TokenTree};
1213
use quote::spanned::Spanned;
1314
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
15+
use venial::{Attribute, Path, PathSegment};
1416

1517
mod kv_parser;
1618
mod list_parser;
@@ -243,8 +245,18 @@ pub(crate) fn extract_cfg_attrs(
243245
attrs: &[venial::Attribute],
244246
) -> impl IntoIterator<Item = &venial::Attribute> {
245247
attrs.iter().filter(|attr| {
246-
attr.get_single_path_segment()
247-
.is_some_and(|name| name == "cfg")
248+
let Some(attr_name) = attr.get_single_path_segment() else {
249+
return false;
250+
};
251+
// #[cfg(condition)]
252+
if attr_name == "cfg" {
253+
return true;
254+
}
255+
// #[cfg_attr(condition, attributes...)], note that there can be multiple attributes seperated by comma.
256+
if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") {
257+
return true;
258+
}
259+
false
248260
})
249261
}
250262

@@ -303,3 +315,92 @@ pub fn venial_parse_meta(
303315

304316
venial::parse_item(input)
305317
}
318+
319+
// ----------------------------------------------------------------------------------------------------------------------------------------------
320+
321+
// util functions for handling #[func]s and #[var(get=f, set=f)]
322+
323+
pub fn make_func_name_constants(funcs: &[FuncDefinition], class_name: &Ident) -> Vec<TokenStream> {
324+
funcs
325+
.iter()
326+
.map(|func| {
327+
// The constant needs the same #[cfg] attribute(s) as the function, so that it is only active if the function is also active.
328+
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
329+
.into_iter()
330+
.collect::<Vec<_>>();
331+
332+
make_func_name_constant(
333+
class_name,
334+
&func.signature_info.method_name,
335+
func.registered_name.as_ref(),
336+
&cfg_attributes,
337+
)
338+
})
339+
.collect()
340+
}
341+
342+
/// Funcs can be renamed with `#[func(rename=new_name) fn f();`.
343+
/// To be able to access the renamed function name at a later point, it is saved in a string constant.
344+
pub fn make_func_name_constant(
345+
class_name: &Ident,
346+
func_name: &Ident,
347+
registered_name: Option<&String>,
348+
attributes: &[&Attribute],
349+
) -> TokenStream {
350+
let const_name = format_func_name_constant_name(class_name, func_name);
351+
let const_value = match &registered_name {
352+
Some(renamed) => renamed.to_string(),
353+
None => func_name.to_string(),
354+
};
355+
356+
let doc_comment =
357+
format!("The Rust function `{func_name}` is registered with Godot as `{const_value}`.");
358+
359+
quote! {
360+
#(#attributes)*
361+
#[doc = #doc_comment]
362+
#[doc(hidden)]
363+
#[allow(non_upper_case_globals)]
364+
pub const #const_name: &str = #const_value;
365+
}
366+
}
367+
368+
/// Converts "path::class" to "path::new_class".
369+
pub fn replace_class_in_path(path: Path, new_class: Ident) -> Path {
370+
match path.segments.as_slice() {
371+
// Can't happen, you have at least one segment (the class name).
372+
[] => panic!("unexpected: empty path"),
373+
374+
[_single] => Path {
375+
segments: vec![PathSegment {
376+
ident: new_class,
377+
generic_args: None,
378+
tk_separator_colons: None,
379+
}],
380+
},
381+
382+
[path @ .., _last] => {
383+
let mut segments = vec![];
384+
segments.extend(path.iter().cloned());
385+
segments.push(PathSegment {
386+
ident: new_class,
387+
generic_args: None,
388+
tk_separator_colons: Some([
389+
Punct::new(':', Spacing::Joint),
390+
Punct::new(':', Spacing::Alone),
391+
]),
392+
});
393+
Path { segments }
394+
}
395+
}
396+
}
397+
398+
/// Returns the name of the constant that will be autogenerated.
399+
pub fn format_func_name_constant_name(_class_name: &Ident, func_name: &Ident) -> Ident {
400+
format_ident!("{func_name}")
401+
}
402+
403+
/// Returns the name of the dummy struct that's used as container for all function name constants.
404+
pub fn format_func_name_struct_name(class_name: &Ident) -> Ident {
405+
format_ident!("__gdext_{class_name}_Functions")
406+
}

0 commit comments

Comments
 (0)