Skip to content

Commit 55a7d99

Browse files
authored
Merge pull request #1019 from 0x53A/dev-handle-func-rename
in `#[var]s`, handle renamed `#[func]`s
2 parents f50d8e7 + 22127f5 commit 55a7d99

File tree

10 files changed

+340
-27
lines changed

10 files changed

+340
-27
lines changed

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

+7
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_funcs_collection_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 funcs_collection_constant: TokenStream,
169171
}
170172

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

211+
let funcs_collection_constant =
212+
make_funcs_collection_constant(class_name, &function_name, None, &[]);
213+
209214
let signature = util::parse_signature(signature);
210215
let export_token = make_method_registration(
211216
class_name,
@@ -230,6 +235,7 @@ impl GetterSetterImpl {
230235
function_name,
231236
function_impl,
232237
export_token,
238+
funcs_collection_constant,
233239
}
234240
}
235241

@@ -238,6 +244,7 @@ impl GetterSetterImpl {
238244
function_name: function_name.clone(),
239245
function_impl: TokenStream::new(),
240246
export_token: make_existence_check(function_name),
247+
funcs_collection_constant: TokenStream::new(),
241248
}
242249
}
243250
}

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ 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, format_funcs_collection_struct, ident, make_funcs_collection_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};
@@ -75,6 +78,7 @@ pub struct InherentImplAttr {
7578
pub fn transform_inherent_impl(
7679
meta: InherentImplAttr,
7780
mut impl_block: venial::Impl,
81+
self_path: venial::Path,
7882
) -> ParseResult<TokenStream> {
7983
let class_name = util::validate_impl(&impl_block, None, "godot_api")?;
8084
let class_name_obj = util::class_name_obj(&class_name);
@@ -89,6 +93,15 @@ pub fn transform_inherent_impl(
8993
#[cfg(not(all(feature = "register-docs", since_api = "4.3")))]
9094
let docs = quote! {};
9195

96+
// Container struct holding names of all registered #[func]s.
97+
// The struct is declared by #[derive(GodotClass)].
98+
let funcs_collection = {
99+
let struct_name = format_funcs_collection_struct(&class_name);
100+
replace_class_in_path(self_path, struct_name)
101+
};
102+
103+
// For each #[func] in this impl block, create one constant.
104+
let func_name_constants = make_funcs_collection_constants(&funcs, &class_name);
92105
let signal_registrations = make_signal_registrations(signals, &class_name_obj);
93106

94107
#[cfg(feature = "codegen-full")]
@@ -164,6 +177,9 @@ pub fn transform_inherent_impl(
164177
#trait_impl
165178
#fill_storage
166179
#class_registration
180+
impl #funcs_collection {
181+
#( #func_name_constants )*
182+
}
167183
};
168184

169185
Ok(result)
@@ -174,6 +190,9 @@ pub fn transform_inherent_impl(
174190
let result = quote! {
175191
#impl_block
176192
#fill_storage
193+
impl #funcs_collection {
194+
#( #func_name_constants )*
195+
}
177196
};
178197

179198
Ok(result)

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

+38-20
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
//! Parsing the `var` and `export` attributes on fields.
8+
//! Parses the `#[var]` and `#[export]` attributes on fields.
99
1010
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags};
11+
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct};
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 declaration.
168+
// This is the name of the container struct, which is declared by #[derive(GodotClass)].
169+
let class_functions_name = format_funcs_collection_struct(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,22 @@ 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 {
181-
if let Some(getter_impl) = getter_setter_impl {
182-
let GetterSetterImpl {
183-
function_name,
184-
function_impl,
185-
export_token,
186-
} = getter_impl;
187-
188-
getter_setter_impls.push(function_impl);
189-
export_tokens.push(export_token);
190-
191-
function_name.to_string()
192-
} else {
193-
String::new()
194-
}
197+
class_name: &Ident,
198+
) -> TokenStream {
199+
let Some(gs) = getter_setter_impl else {
200+
return quote! { "" };
201+
};
202+
203+
getter_setter_impls.push(gs.function_impl);
204+
func_name_consts.push(gs.funcs_collection_constant);
205+
export_tokens.push(gs.export_token);
206+
207+
// Getters/setters are, like #[func]s, subject to additional code generation: a constant inside a "funcs collection" struct
208+
// stores their Godot name and can be used as an indirection to refer to their true name from other procedural macros.
209+
let funcs_collection = format_funcs_collection_struct(class_name);
210+
let constant = format_funcs_collection_constant(class_name, &gs.function_name);
211+
212+
quote! { #funcs_collection::#constant }
195213
}

godot-macros/src/class/derive_godot_class.rs

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

140+
// Declares a "funcs collection" struct that, for holds a constant for each #[func].
141+
// That constant maps the Rust name (constant ident) to the Godot registered name (string value).
142+
let funcs_collection_struct_name = format_funcs_collection_struct(class_name);
143+
let funcs_collection_struct = quote! {
144+
#[doc(hidden)]
145+
pub struct #funcs_collection_struct_name {}
146+
};
147+
137148
Ok(quote! {
138149
impl ::godot::obj::GodotClass for #class_name {
139150
type Base = #base_class;
@@ -157,6 +168,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
157168
type Exportable = <<Self as ::godot::obj::GodotClass>::Base as ::godot::obj::Bounds>::Exportable;
158169
}
159170

171+
#funcs_collection_struct
160172
#godot_init_impl
161173
#godot_withbase_impl
162174
#godot_exports_impl

godot-macros/src/class/godot_api.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub fn attribute_godot_api(
4141
)?;
4242
}
4343

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

@@ -57,7 +57,7 @@ pub fn attribute_godot_api(
5757
transform_trait_impl(decl)
5858
} else {
5959
match parse_inherent_impl_attr(meta) {
60-
Ok(meta) => transform_inherent_impl(meta, decl),
60+
Ok(meta) => transform_inherent_impl(meta, decl, self_path),
6161
Err(err) => Err(err),
6262
}
6363
}

godot-macros/src/util/mod.rs

+111-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
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};
1415

@@ -243,8 +244,21 @@ pub(crate) fn extract_cfg_attrs(
243244
attrs: &[venial::Attribute],
244245
) -> impl IntoIterator<Item = &venial::Attribute> {
245246
attrs.iter().filter(|attr| {
246-
attr.get_single_path_segment()
247-
.is_some_and(|name| name == "cfg")
247+
let Some(attr_name) = attr.get_single_path_segment() else {
248+
return false;
249+
};
250+
251+
// #[cfg(condition)]
252+
if attr_name == "cfg" {
253+
return true;
254+
}
255+
256+
// #[cfg_attr(condition, attributes...)]. Multiple attributes can be seperated by comma.
257+
if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") {
258+
return true;
259+
}
260+
261+
false
248262
})
249263
}
250264

@@ -303,3 +317,97 @@ pub fn venial_parse_meta(
303317

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

0 commit comments

Comments
 (0)