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

Allow #[cfg] to be used with #[godot_api] - Part 1: inherent impls #443

Merged
merged 3 commits into from
Oct 14, 2023
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
6 changes: 6 additions & 0 deletions godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ impl GetterSetterImpl {
class_name,
FuncDefinition {
func: signature,
// Since we're analyzing a struct's field, we don't have access to the corresponding get/set function's
// external (non-#[func]) attributes. We have to assume the function exists and has the name the user
// gave us, with the expected signature.
// Ideally, we'd be able to place #[cfg_attr] on #[var(get)] and #[var(set)] to be able to match a
// #[cfg()] (for instance) placed on the getter/setter function, but that is not currently supported.
external_attributes: Vec::new(),
rename: None,
has_gd_self: false,
},
Expand Down
9 changes: 9 additions & 0 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use quote::{format_ident, quote};
pub struct FuncDefinition {
/// Raw information about the Rust function.
pub func: venial::Function,
/// The function's non-gdext attributes (all except #[func]).
pub external_attributes: Vec<venial::Attribute>,
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
pub rename: Option<String>,
pub has_gd_self: bool,
Expand Down Expand Up @@ -78,7 +80,14 @@ pub fn make_method_registration(
};
let param_ident_strs = param_idents.iter().map(|ident| ident.to_string());

// Transport #[cfg] attrs to the FFI glue to ensure functions which were conditionally
// removed from compilation don't cause errors.
let cfg_attrs = util::extract_cfg_attrs(&func_definition.external_attributes)
.into_iter()
.collect::<Vec<_>>();

quote! {
#(#cfg_attrs)*
{
use ::godot::obj::GodotClass;
use ::godot::builtin::meta::registration::method::MethodInfo;
Expand Down
83 changes: 63 additions & 20 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,35 @@ impl BoundAttr {
}
}

/// Holds information known from a signal's definition
struct SignalDefinition {
/// The signal's function signature.
signature: Function,

/// The signal's non-gdext attributes (all except #[signal]).
external_attributes: Vec<Attribute>,
}

/// Codegen for `#[godot_api] impl MyType`
fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
let class_name = util::validate_impl(&decl, None, "godot_api")?;
let class_name_obj = util::class_name_obj(&class_name);
let (funcs, signals) = process_godot_fns(&mut decl)?;

let mut signal_cfg_attrs: Vec<Vec<&Attribute>> = Vec::new();
let mut signal_name_strs: Vec<String> = Vec::new();
let mut signal_parameters_count: Vec<usize> = Vec::new();
let mut signal_parameters: Vec<TokenStream> = Vec::new();

for signature in signals {
for signal in signals.iter() {
let SignalDefinition {
signature,
external_attributes,
} = signal;
let mut param_types: Vec<TyExpr> = Vec::new();
let mut param_names: Vec<String> = Vec::new();

for param in signature.params.inner {
for param in signature.params.inner.iter() {
match &param.0 {
FnParam::Typed(param) => {
param_types.push(param.ty.clone());
Expand All @@ -103,6 +117,13 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
]
};

// Transport #[cfg] attrs to the FFI glue to ensure signals which were conditionally
// removed from compilation don't cause errors.
signal_cfg_attrs.push(
util::extract_cfg_attrs(external_attributes)
.into_iter()
.collect(),
);
signal_name_strs.push(signature.name.to_string());
signal_parameters_count.push(param_names.len());
signal_parameters.push(param_array_decl);
Expand All @@ -115,6 +136,7 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
.map(|func_def| make_method_registration(&class_name, func_def));

let consts = process_godot_constants(&mut decl)?;
let mut integer_constant_cfg_attrs = Vec::new();
let mut integer_constant_names = Vec::new();
let mut integer_constant_values = Vec::new();

Expand All @@ -125,6 +147,15 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {

let name = &constant.name;

// Unlike with #[func] and #[signal], we don't remove the attributes from Constant
// signatures within 'process_godot_constants'.
let cfg_attrs = util::extract_cfg_attrs(&constant.attributes)
.into_iter()
.collect::<Vec<_>>();

// Transport #[cfg] attrs to the FFI glue to ensure constants which were conditionally
// removed from compilation don't cause errors.
integer_constant_cfg_attrs.push(cfg_attrs);
integer_constant_names.push(constant.name.to_string());
integer_constant_values.push(quote! { #class_name::#name });
}
Expand All @@ -136,6 +167,7 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
use ::godot::builtin::StringName;

#(
#(#integer_constant_cfg_attrs)*
ExportConstant::new(
#class_name_obj,
ConstantKind::Integer(
Expand Down Expand Up @@ -164,20 +196,23 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
use ::godot::sys;

#(
let parameters_info: [::godot::builtin::meta::PropertyInfo; #signal_parameters_count] = #signal_parameters;

let mut parameters_info_sys: [::godot::sys::GDExtensionPropertyInfo; #signal_parameters_count] =
std::array::from_fn(|i| parameters_info[i].property_sys());

let signal_name = ::godot::builtin::StringName::from(#signal_name_strs);

sys::interface_fn!(classdb_register_extension_class_signal)(
sys::get_library(),
#class_name_obj.string_sys(),
signal_name.string_sys(),
parameters_info_sys.as_ptr(),
sys::GDExtensionInt::from(#signal_parameters_count as i64),
);
#(#signal_cfg_attrs)*
{
let parameters_info: [::godot::builtin::meta::PropertyInfo; #signal_parameters_count] = #signal_parameters;

let mut parameters_info_sys: [::godot::sys::GDExtensionPropertyInfo; #signal_parameters_count] =
std::array::from_fn(|i| parameters_info[i].property_sys());

let signal_name = ::godot::builtin::StringName::from(#signal_name_strs);

sys::interface_fn!(classdb_register_extension_class_signal)(
sys::get_library(),
#class_name_obj.string_sys(),
signal_name.string_sys(),
parameters_info_sys.as_ptr(),
sys::GDExtensionInt::from(#signal_parameters_count as i64),
);
};
)*
}
}
Expand All @@ -203,9 +238,11 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
Ok(result)
}

fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Function>), Error> {
fn process_godot_fns(
decl: &mut Impl,
) -> Result<(Vec<FuncDefinition>, Vec<SignalDefinition>), Error> {
let mut func_definitions = vec![];
let mut signal_signatures = vec![];
let mut signal_definitions = vec![];

let mut removed_indexes = vec![];
for (index, item) in decl.body_items.iter_mut().enumerate() {
Expand Down Expand Up @@ -238,6 +275,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
rename,
has_gd_self,
} => {
let external_attributes = method.attributes.clone();
// Signatures are the same thing without body
let mut sig = util::reduce_to_signature(method);
if *has_gd_self {
Expand All @@ -249,6 +287,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
}
func_definitions.push(FuncDefinition {
func: sig,
external_attributes,
rename: rename.clone(),
has_gd_self: *has_gd_self,
});
Expand All @@ -257,9 +296,13 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
if method.return_ty.is_some() {
return attr.bail("return types are not supported", method);
}
let external_attributes = method.attributes.clone();
let sig = util::reduce_to_signature(method);

signal_signatures.push(sig.clone());
signal_definitions.push(SignalDefinition {
signature: sig,
external_attributes,
});
removed_indexes.push(index);
}
BoundAttrType::Const(_) => {
Expand All @@ -278,7 +321,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Functi
decl.body_items.remove(index);
}

Ok((func_definitions, signal_signatures))
Ok((func_definitions, signal_definitions))
}

fn process_godot_constants(decl: &mut Impl) -> Result<Vec<Constant>, Error> {
Expand Down
9 changes: 9 additions & 0 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ pub(crate) fn path_ends_with(path: &[TokenTree], expected: &str) -> bool {
.unwrap_or(false)
}

pub(crate) fn extract_cfg_attrs(
attrs: &[venial::Attribute],
) -> impl IntoIterator<Item = &venial::Attribute> {
attrs.iter().filter(|attr| {
attr.get_single_path_segment()
.map_or(false, |name| name == "cfg")
})
}

pub(crate) struct DeclInfo {
pub where_: Option<WhereClause>,
pub generic_params: Option<GenericParamList>,
Expand Down
42 changes: 41 additions & 1 deletion itest/rust/src/register_tests/constant_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,45 @@ impl HasConstants {
#[constant]
#[cfg(all())]
const CONSTANT_RECOGNIZED_WITH_SIMPLE_PATH_ATTRIBUTE_BELOW_CONST_ATTR: bool = true;

// The three identically-named definitions below should be mutually exclusive thanks to #[cfg].
#[constant]
const CFG_REMOVES_DUPLICATE_CONSTANT_DEF: i64 = 5;

#[cfg(any())]
#[constant]
const CFG_REMOVES_DUPLICATE_CONSTANT_DEF: i64 = compile_error!("Removed by #[cfg]");

#[constant]
#[cfg(any())]
const CFG_REMOVES_DUPLICATE_CONSTANT_DEF: i64 = compile_error!("Removed by #[cfg]");

// The constant below should end up not being defined at all.
#[cfg(any())]
#[constant]
const CFG_REMOVES_CONSTANT: bool = compile_error!("Removed by #[cfg]");

#[constant]
#[cfg(any())]
const CFG_REMOVES_CONSTANT: bool = compile_error!("Removed by #[cfg]");
}

/// Checks at runtime if a class has a given integer constant through [ClassDb].
fn class_has_integer_constant<T: GodotClass>(name: &str) -> bool {
ClassDb::singleton().class_has_integer_constant(T::class_name().to_string_name(), name.into())
}

#[itest]
fn constants_correct_value() {
const CONSTANTS: [(&str, i64); 4] = [
const CONSTANTS: [(&str, i64); 5] = [
("A", HasConstants::A),
("B", HasConstants::B as i64),
("C", HasConstants::C as i64),
("D", HasConstants::D as i64),
(
"CFG_REMOVES_DUPLICATE_CONSTANT_DEF",
HasConstants::CFG_REMOVES_DUPLICATE_CONSTANT_DEF,
),
];

let constants = ClassDb::singleton()
Expand All @@ -72,6 +102,16 @@ fn constants_correct_value() {
static_assert!(HasConstants::CONSTANT_RECOGNIZED_WITH_SIMPLE_PATH_ATTRIBUTE_BELOW_CONST_ATTR);
}

#[itest]
fn cfg_removes_or_keeps_constants() {
assert!(class_has_integer_constant::<HasConstants>(
"CFG_REMOVES_DUPLICATE_CONSTANT_DEF"
));
assert!(!class_has_integer_constant::<HasConstants>(
"CFG_REMOVES_CONSTANT"
));
}

#[derive(GodotClass)]
struct HasOtherConstants {}

Expand Down
Loading