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

Add ability to rename functions bound to Godot #376

Closed
Closed
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
9 changes: 8 additions & 1 deletion godot-macros/src/derive_godot_class/property/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use crate::derive_godot_class::{make_existence_check, Field, FieldHint};
use crate::method_registration::make_method_registration;
use crate::method_registration::FuncDefinition;
use crate::util::KvParser;
use crate::{util, ParseResult};
use proc_macro2::{Ident, TokenStream};
Expand Down Expand Up @@ -188,7 +189,13 @@ impl GetterSetterImpl {
};

let signature = util::parse_signature(signature);
let export_token = make_method_registration(class_name, signature);
let export_token = make_method_registration(
class_name,
FuncDefinition {
func: signature,
rename: None,
},
);

Self {
function_name,
Expand Down
190 changes: 130 additions & 60 deletions godot-macros/src/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::method_registration::{make_method_registration, make_virtual_method_callback};
use crate::method_registration::{
make_method_registration, make_virtual_method_callback, FuncDefinition,
};
use crate::util;
use crate::util::bail;
use proc_macro2::{Ident, TokenStream};
use crate::util::KvParser;
use proc_macro2::TokenStream;
use quote::quote;
use quote::spanned::Spanned;
use venial::{
Attribute, AttributeValue, Constant, Declaration, Error, FnParam, Function, Impl, ImplMember,
TyExpr,
Attribute, Constant, Declaration, Error, FnParam, Function, Impl, ImplMember, TyExpr,
};

pub fn transform(input_decl: Declaration) -> Result<TokenStream, Error> {
Expand Down Expand Up @@ -46,23 +48,25 @@ pub fn transform(input_decl: Declaration) -> Result<TokenStream, Error> {

/// Attribute for user-declared function
enum BoundAttrType {
Func(AttributeValue),
Signal(AttributeValue),
Const(AttributeValue),
Func { rename: Option<String> },
Signal,
Const,
}

struct BoundAttr {
attr_name: Ident,
struct BoundAttr<'a> {
attr_name: &'a str,
index: usize,
ty: BoundAttrType,
}

impl BoundAttr {
impl BoundAttr<'_> {
fn bail<R>(self, msg: &str, method: &Function) -> Result<R, Error> {
bail!(&method.name, "#[{}]: {}", self.attr_name, msg)
}
}

type Signal = Function;
Copy link
Member

@Bromeon Bromeon Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably keep this as a separate type, as the possible options for #[func] and #[signal] likely diverge over time. It also slightly enhances type safety.

Signal renames are not yet supported as far as I can see, so they should not have this field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not worth it to rename the type for a bit more clarity in the method signature for?

fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Signal>), Error>

vs

fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<FuncDefinition>, Vec<Function>), Error>


/// 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")?;
Expand Down Expand Up @@ -108,7 +112,7 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {

let methods_registration = funcs
.into_iter()
.map(|func| make_method_registration(&class_name, func));
.map(|func_def| make_method_registration(&class_name, func_def));

let consts = process_godot_constants(&mut decl)?;
let mut integer_constant_names = Vec::new();
Expand Down Expand Up @@ -198,8 +202,8 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
Ok(result)
}

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

let mut removed_indexes = vec![];
Expand All @@ -210,7 +214,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Function>),
continue;
};

if let Some(attr) = extract_attributes(&method, &method.attributes)? {
if let Some(attr) = extract_attributes(&method, &method.attributes.clone())? {
// Remaining code no longer has attribute -- rest stays
method.attributes.remove(attr.index);

Expand All @@ -229,12 +233,12 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Function>),
}

match attr.ty {
BoundAttrType::Func(_attr) => {
BoundAttrType::Func { rename } => {
// Signatures are the same thing without body
let sig = util::reduce_to_signature(method);
func_signatures.push(sig);
func_definitions.push(FuncDefinition { func: sig, rename });
}
BoundAttrType::Signal(ref _attr_val) => {
BoundAttrType::Signal => {
if method.return_ty.is_some() {
return attr.bail("return types are not supported", method);
}
Expand All @@ -243,7 +247,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Function>),
signal_signatures.push(sig.clone());
removed_indexes.push(index);
}
BoundAttrType::Const(_) => {
BoundAttrType::Const => {
return attr.bail(
"#[constant] can only be used on associated constant",
method,
Expand All @@ -259,7 +263,7 @@ fn process_godot_fns(decl: &mut Impl) -> Result<(Vec<Function>, Vec<Function>),
decl.body_items.remove(index);
}

Ok((func_signatures, signal_signatures))
Ok((func_definitions, signal_signatures))
}

fn process_godot_constants(decl: &mut Impl) -> Result<Vec<Constant>, Error> {
Expand All @@ -270,18 +274,18 @@ fn process_godot_constants(decl: &mut Impl) -> Result<Vec<Constant>, Error> {
continue;
};

if let Some(attr) = extract_attributes(&constant, &constant.attributes)? {
if let Some(attr) = extract_attributes(&constant, &constant.attributes.clone())? {
// Remaining code no longer has attribute -- rest stays
constant.attributes.remove(attr.index);

match attr.ty {
BoundAttrType::Func(_) => {
BoundAttrType::Func { .. } => {
return bail!(constant, "#[func] can only be used on functions")
}
BoundAttrType::Signal(_) => {
BoundAttrType::Signal => {
return bail!(constant, "#[signal] can only be used on functions")
}
BoundAttrType::Const(_) => {
BoundAttrType::Const => {
if constant.initializer.is_none() {
return bail!(constant, "exported constant must have initializer");
}
Expand All @@ -301,48 +305,114 @@ fn extract_attributes<T>(
where
for<'a> &'a T: Spanned,
{
let mut found = None;
for (index, attr) in attributes.iter().enumerate() {
let attr_name = attr
.get_single_path_segment()
.expect("get_single_path_segment");

let new_found = match attr_name {
name if name == "func" => Some(BoundAttr {
attr_name: attr_name.clone(),
index,
ty: BoundAttrType::Func(attr.value.clone()),
}),
name if name == "signal" => {
// TODO once parameters are supported, this should probably be moved to the struct definition
// E.g. a zero-sized type Signal<(i32, String)> with a provided emit(i32, String) method
// This could even be made public (callable on the struct obj itself)
Some(BoundAttr {
attr_name: attr_name.clone(),
index,
ty: BoundAttrType::Signal(attr.value.clone()),
})
}
name if name == "constant" => Some(BoundAttr {
attr_name: attr_name.clone(),
index,
ty: BoundAttrType::Const(attr.value.clone()),
}),
_ => None,
};
const FUNC: &str = "func";
const SIGNAL: &str = "signal";
const CONSTANT: &str = "constant";

let mut name_and_parser = None;

// Validate at most 1 attribute
if found.is_some() && new_found.is_some() {
bail!(
&error_scope,
"at most one #[func], #[signal], or #[constant] attribute per declaration allowed",
)?;
let mut found_attr_count = 0;
for name in [FUNC, SIGNAL, CONSTANT] {
if let Some(valid_parser) = KvParser::parse(attributes, name)? {
found_attr_count += 1;
name_and_parser = Some((name, valid_parser));
}
}

found = new_found;
if found_attr_count > 1 {
bail!(
&error_scope,
"at most one #[func], #[signal], or #[constant] attribute per declaration allowed",
)?;
}

Ok(found)
let Some((name, mut parser)) = name_and_parser else {
return Ok(None);
};

let index = parser.index();

let attr = match name {
FUNC => {
// TODO you-win (August 8, 2023): handle default values here as well?

let rename = parser.handle_expr("rename")?.map(|value| value.to_string());

Some(BoundAttr {
attr_name: name,
index,
ty: BoundAttrType::Func { rename },
})
}
SIGNAL => Some(BoundAttr {
attr_name: name,
index,
ty: BoundAttrType::Signal,
}),
CONSTANT => Some(BoundAttr {
attr_name: name,
index,
ty: BoundAttrType::Const,
}),
_ => unreachable!(),
};

parser.finish()?;

Ok(attr)

// let mut found = None;
// for (_index, attr) in attributes.iter().enumerate() {
// let attr_name = attr
// .get_single_path_segment()
// .expect("get_single_path_segment");

// let new_found = match attr_name {
// name if name == "func" => {
// // TODO you-win (August 8, 2023): handle default values here as well?

// // Safe unwrap since #[func] must be present if we got to this point
// let mut parser = KvParser::parse(attributes, "func")?.unwrap();

// let rename = if let Ok(Some(value)) = parser.handle_expr("rename") {
// Some(value.to_string())
// } else {
// None
// };

// Some(BoundAttr {
// attr_name: attr_name.clone(),
// ty: BoundAttrType::Func { rename },
// })
// }
// name if name == "signal" => {
// // TODO once parameters are supported, this should probably be moved to the struct definition
// // E.g. a zero-sized type Signal<(i32, String)> with a provided emit(i32, String) method
// // This could even be made public (callable on the struct obj itself)
// Some(BoundAttr {
// attr_name: attr_name.clone(),
// ty: BoundAttrType::Signal,
// })
// }
// name if name == "constant" => Some(BoundAttr {
// attr_name: attr_name.clone(),
// ty: BoundAttrType::Const,
// }),
// _ => None,
// };

// // Validate at most 1 attribute
// if found.is_some() && new_found.is_some() {
// bail!(
// &error_scope,
// "at most one #[func], #[signal], or #[constant] attribute per declaration allowed",
// )?;
// }

// found = new_found;
// }

// Ok(found)
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions godot-macros/src/method_registration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ struct SignatureInfo {
pub ret_type: TokenStream,
}

/// Information used for registering a Rust function with Godot.
pub struct FuncDefinition {
/// Raw information about the Rust function.
pub func: venial::Function,
/// The name the function will be exposed as in Godot. If `None`, the Rust function name is used.
pub rename: Option<String>,
}

/// Returns a closure expression that forwards the parameters to the Rust instance.
fn make_forwarding_closure(class_name: &Ident, signature_info: &SignatureInfo) -> TokenStream {
let method_name = &signature_info.method_name;
Expand Down
10 changes: 7 additions & 3 deletions godot-macros/src/method_registration/register_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use quote::quote;
/// Generates code that registers the specified method for the given class.
pub fn make_method_registration(
class_name: &Ident,
method_signature: venial::Function,
func_definition: super::FuncDefinition,
) -> TokenStream {
let signature_info = get_signature_info(&method_signature);
let signature_info = get_signature_info(&func_definition.func);
let sig_tuple =
util::make_signature_tuple_type(&signature_info.ret_type, &signature_info.param_types);

Expand All @@ -33,7 +33,11 @@ pub fn make_method_registration(

// String literals
let class_name_str = class_name.to_string();
let method_name_str = method_name.to_string();
let method_name_str = if let Some(rename) = func_definition.rename {
rename
} else {
method_name.to_string()
};
let param_ident_strs = param_idents.iter().map(|ident| ident.to_string());

quote! {
Expand Down
8 changes: 7 additions & 1 deletion godot-macros/src/util/kv_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) type KvMap = HashMap<Ident, Option<KvValue>>;
pub(crate) struct KvParser {
map: KvMap,
span: Span,
index: usize,
}

#[allow(dead_code)] // some functions will be used later
Expand All @@ -41,7 +42,7 @@ impl KvParser {
pub fn parse(attributes: &[Attribute], expected: &str) -> ParseResult<Option<Self>> {
let mut found_attr: Option<Self> = None;

for attr in attributes.iter() {
for (index, attr) in attributes.iter().enumerate() {
let path = &attr.path;
if path_is_single(path, expected) {
if found_attr.is_some() {
Expand All @@ -51,6 +52,7 @@ impl KvParser {
let attr_name = expected.to_string();
found_attr = Some(Self {
span: attr.tk_brackets.span,
index,
map: ParserState::parse(attr_name, &attr.value)?,
});
}
Expand All @@ -63,6 +65,10 @@ impl KvParser {
self.span
}

pub fn index(&self) -> usize {
self.index
}

/// - For missing keys, returns `None`.
/// - For a key with no value, returns `Some(None)`.
/// - For a key with a value, returns `Some(value)`.
Expand Down
Loading