Skip to content

Commit cf338ec

Browse files
committed
Allow #[cfg] to be used with #[func]
1 parent 3491d7b commit cf338ec

File tree

5 files changed

+110
-0
lines changed

5 files changed

+110
-0
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ impl GetterSetterImpl {
194194
class_name,
195195
FuncDefinition {
196196
func: signature,
197+
// Since we're analyzing a struct's field, we don't have access to the corresponding get/set function's
198+
// external (non-#[func]) attributes. We have to assume the function exists and has the name the user
199+
// gave us, with the expected signature.
200+
// Ideally, we'd be able to place #[cfg_attr] on #[var(get)] and #[var(set)] to be able to match a
201+
// #[cfg()] (for instance) placed on the getter/setter function, but that is not currently supported.
202+
external_attributes: Vec::new(),
197203
rename: None,
198204
has_gd_self: false,
199205
},

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

+9
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use quote::{format_ident, quote};
1212
pub struct FuncDefinition {
1313
/// Raw information about the Rust function.
1414
pub func: venial::Function,
15+
/// The function's non-gdext attributes (that is, excluding #[func]).
16+
pub external_attributes: Vec<venial::Attribute>,
1517
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
1618
pub rename: Option<String>,
1719
pub has_gd_self: bool,
@@ -78,7 +80,14 @@ pub fn make_method_registration(
7880
};
7981
let param_ident_strs = param_idents.iter().map(|ident| ident.to_string());
8082

83+
// Transport #[cfg] attrs to the FFI glue to ensure functions which were conditionally
84+
// removed from compilation don't cause errors.
85+
let cfg_attrs = util::extract_cfg_attrs(&func_definition.external_attributes)
86+
.into_iter()
87+
.collect::<Vec<_>>();
88+
8189
quote! {
90+
#(#cfg_attrs)*
8291
{
8392
use ::godot::obj::GodotClass;
8493
use ::godot::builtin::meta::registration::method::MethodInfo;

godot-macros/src/class/godot_api.rs

+2
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
238238
rename,
239239
has_gd_self,
240240
} => {
241+
let external_attributes = method.attributes.clone();
241242
// Signatures are the same thing without body
242243
let mut sig = util::reduce_to_signature(method);
243244
if *has_gd_self {
@@ -249,6 +250,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
249250
}
250251
func_definitions.push(FuncDefinition {
251252
func: sig,
253+
external_attributes,
252254
rename: rename.clone(),
253255
has_gd_self: *has_gd_self,
254256
});

godot-macros/src/util/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ pub(crate) fn path_ends_with(path: &[TokenTree], expected: &str) -> bool {
227227
.unwrap_or(false)
228228
}
229229

230+
pub(crate) fn extract_cfg_attrs(
231+
attrs: &[venial::Attribute],
232+
) -> impl IntoIterator<Item = &venial::Attribute> {
233+
attrs.iter().filter(|attr| {
234+
attr.get_single_path_segment()
235+
.map_or(false, |name| name == "cfg")
236+
})
237+
}
238+
230239
pub(crate) struct DeclInfo {
231240
pub where_: Option<WhereClause>,
232241
pub generic_params: Option<GenericParamList>,

itest/rust/src/register_tests/func_test.rs

+84
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// Needed for Clippy to accept #[cfg(all())]
88
#![allow(clippy::non_minimal_cfg)]
99

10+
use crate::framework::itest;
11+
use godot::engine::ClassDb;
1012
use godot::prelude::*;
1113

1214
#[derive(GodotClass)]
@@ -29,6 +31,26 @@ impl FuncRename {
2931
fn renamed_static() -> GodotString {
3032
GodotString::from("static")
3133
}
34+
35+
#[cfg(all())]
36+
fn returns_hello_world(&self) -> GodotString {
37+
GodotString::from("Hello world!")
38+
}
39+
40+
#[cfg(any())]
41+
fn returns_hello_world(&self) -> GodotString {
42+
compile_error!("Removed by #[cfg]")
43+
}
44+
45+
#[cfg(any())]
46+
fn returns_bye_world(&self) -> GodotString {
47+
compile_error!("Removed by #[cfg]")
48+
}
49+
50+
#[cfg(all())]
51+
fn returns_bye_world(&self) -> GodotString {
52+
GodotString::from("Bye world!")
53+
}
3254
}
3355

3456
impl FuncRename {
@@ -89,6 +111,35 @@ impl GdSelfReference {
89111
f1 && f2
90112
}
91113

114+
#[func]
115+
fn cfg_removes_function() -> bool {
116+
true
117+
}
118+
119+
#[cfg(any())]
120+
#[func]
121+
fn cfg_removes_function() -> bool {
122+
false
123+
}
124+
125+
#[func]
126+
#[cfg(any())]
127+
fn cfg_removes_function() -> bool {
128+
false
129+
}
130+
131+
#[cfg(any())]
132+
#[func]
133+
fn cfg_removes_function_ffi_glue() -> bool {
134+
true
135+
}
136+
137+
#[func]
138+
#[cfg(any())]
139+
fn cfg_removes_function_ffi_glue() -> bool {
140+
true
141+
}
142+
92143
#[signal]
93144
#[rustfmt::skip]
94145
fn signal_shouldnt_panic_with_segmented_path_attribute();
@@ -148,3 +199,36 @@ impl RefCountedVirtual for GdSelfReference {
148199
}
149200
}
150201
}
202+
203+
#[itest]
204+
fn cfg_doesnt_interfere_with_valid_method_impls() {
205+
// if we re-implement this method but the re-implementation is removed, that should keep the non-removed
206+
// implementation.
207+
let object = Gd::new(FuncRename);
208+
assert_eq!(
209+
object.bind().returns_hello_world(),
210+
GodotString::from("Hello world!")
211+
);
212+
assert_eq!(
213+
object.bind().returns_bye_world(),
214+
GodotString::from("Bye world!")
215+
);
216+
}
217+
218+
#[itest]
219+
fn cfg_removes_or_keeps_methods() {
220+
let has_method = |name: &str| {
221+
ClassDb::singleton()
222+
.class_has_method_ex(GdSelfReference::class_name().to_string_name(), name.into())
223+
.no_inheritance(true)
224+
.done()
225+
};
226+
assert!(has_method(
227+
"func_recognized_with_simple_path_attribute_above_func_attr"
228+
));
229+
assert!(has_method(
230+
"func_recognized_with_simple_path_attribute_below_func_attr"
231+
));
232+
assert!(has_method("cfg_removes_function"));
233+
assert!(!has_method("cfg_removes_function_ffi_glue"));
234+
}

0 commit comments

Comments
 (0)