Skip to content

Commit

Permalink
Merge pull request #443 from PgBiel/allow-cfg-inherent-impl-godot-api
Browse files Browse the repository at this point in the history
Allow #[cfg] to be used with #[godot_api] - Part 1: inherent impls
  • Loading branch information
Bromeon authored Oct 14, 2023
2 parents 844f0f3 + 18a4611 commit 8165f62
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 21 deletions.
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

0 comments on commit 8165f62

Please sign in to comment.