Skip to content

Commit 74fab79

Browse files
committed
#[var(get=f, set=f)]: properly handle #[func]s that have been renamed with (rename=godot_name)
1 parent bf4ddd9 commit 74fab79

File tree

9 files changed

+339
-12
lines changed

9 files changed

+339
-12
lines changed

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

Lines changed: 7 additions & 0 deletions
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_function_registered_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_registered_name_const: TokenStream,
169171
}
170172

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

211+
let func_registered_name_const =
212+
make_function_registered_name_constant(class_name, &function_name, None, &[]);
213+
209214
let signature = util::parse_signature(signature);
210215
let export_token = make_method_registration(
211216
class_name,
@@ -229,6 +234,7 @@ impl GetterSetterImpl {
229234
function_name,
230235
function_impl,
231236
export_token,
237+
func_registered_name_const,
232238
}
233239
}
234240

@@ -237,6 +243,7 @@ impl GetterSetterImpl {
237243
function_name: function_name.clone(),
238244
function_impl: TokenStream::new(),
239245
export_token: make_existence_check(function_name),
246+
func_registered_name_const: TokenStream::new(),
240247
}
241248
}
242249
}

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

Lines changed: 24 additions & 1 deletion
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_function_registered_name_struct_name, ident,
15+
make_function_registered_name_constants, 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_function_registered_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_function_registered_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

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
//! Parsing the `var` and `export` attributes on fields.
99
1010
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
11+
use crate::util::{
12+
format_function_registered_name_constant_name, format_function_registered_name_struct_name,
13+
ident,
14+
};
1115
use proc_macro2::{Ident, TokenStream};
1216
use quote::quote;
1317

@@ -38,6 +42,7 @@ impl FieldHint {
3842

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

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

137-
let getter_name = make_getter_setter(
142+
// Note: (getter/setter)_tokens can be either a path ``Class_Functions::constant_name`` or an empty string ``""``.
143+
144+
let getter_tokens = make_getter_setter(
138145
getter.to_impl(class_name, GetSet::Get, field),
139146
&mut getter_setter_impls,
147+
&mut func_registered_name_consts,
140148
&mut export_tokens,
149+
class_name,
141150
);
142-
let setter_name = make_getter_setter(
151+
let setter_tokens = make_getter_setter(
143152
setter.to_impl(class_name, GetSet::Set, field),
144153
&mut getter_setter_impls,
154+
&mut func_registered_name_consts,
145155
&mut export_tokens,
156+
class_name,
146157
);
147158

148159
export_tokens.push(quote! {
149160
::godot::register::private::#registration_fn::<#class_name, #field_type>(
150161
#field_name,
151-
#getter_name,
152-
#setter_name,
162+
#getter_tokens,
163+
#setter_tokens,
153164
#hint,
154165
#usage_flags,
155166
);
156167
});
157168
}
158169

170+
// For each generated #[func], add a const.
171+
// This is the name of the container struct, which is declared by the derive macro GodotClass.
172+
let class_functions_name = format_function_registered_name_struct_name(class_name);
173+
159174
quote! {
160175
impl #class_name {
161176
#(#getter_setter_impls)*
162177
}
163178

179+
impl #class_functions_name {
180+
#(#func_registered_name_consts)*
181+
}
182+
164183
impl ::godot::obj::cap::ImplementsGodotExports for #class_name {
165184
fn __register_exports() {
166185
#(
@@ -176,20 +195,30 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
176195
fn make_getter_setter(
177196
getter_setter_impl: Option<GetterSetterImpl>,
178197
getter_setter_impls: &mut Vec<TokenStream>,
198+
func_registered_name_consts: &mut Vec<TokenStream>,
179199
export_tokens: &mut Vec<TokenStream>,
180-
) -> String {
200+
class_name: &Ident,
201+
) -> TokenStream {
181202
if let Some(getter_impl) = getter_setter_impl {
182203
let GetterSetterImpl {
183204
function_name,
184205
function_impl,
185206
export_token,
207+
func_registered_name_const,
186208
} = getter_impl;
187209

188210
getter_setter_impls.push(function_impl);
211+
func_registered_name_consts.push(func_registered_name_const);
189212
export_tokens.push(export_token);
190213

191-
function_name.to_string()
214+
let getter_setter_name = function_name.to_string();
215+
216+
let class_functions_name = format_function_registered_name_struct_name(class_name);
217+
218+
let getter_setter_fn_const =
219+
format_function_registered_name_constant_name(class_name, &ident(&getter_setter_name));
220+
quote! { #class_functions_name::#getter_setter_fn_const }
192221
} else {
193-
String::new()
222+
quote! { "" }
194223
}
195224
}

godot-macros/src/class/derive_godot_class.rs

Lines changed: 12 additions & 1 deletion
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_function_registered_name_struct_name, ident, path_ends_with_complex,
17+
require_api_version, 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_function_registered_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

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

0 commit comments

Comments
 (0)