-
-
Notifications
You must be signed in to change notification settings - Fork 237
Add groups export. #1214
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
base: master
Are you sure you want to change the base?
Add groups export. #1214
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) godot-rust; Bromeon and contributors. | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use crate::class::Field; | ||
use crate::util::bail; | ||
use crate::ParseResult; | ||
use proc_macro2::{Punct, TokenStream}; | ||
use std::fmt::Display; | ||
|
||
pub struct Fields { | ||
/// All fields except `base_field`. | ||
pub all_fields: Vec<Field>, | ||
|
||
/// The field with type `Base<T>`, if available. | ||
pub base_field: Option<Field>, | ||
|
||
/// Deprecation warnings. | ||
pub deprecations: Vec<TokenStream>, | ||
|
||
/// Errors during macro evaluation that shouldn't abort the execution of the macro. | ||
pub errors: Vec<venial::Error>, | ||
} | ||
|
||
/// Fetches data for all named fields for a struct. | ||
/// | ||
/// Errors if `class` is a tuple struct. | ||
pub fn named_fields( | ||
class: &venial::Struct, | ||
derive_macro_name: impl Display, | ||
) -> ParseResult<Vec<(venial::NamedField, Punct)>> { | ||
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API | ||
// user than those from parse_struct_attributes, so this must be run first. | ||
match &class.fields { | ||
// TODO disallow unit structs in the future | ||
// It often happens that over time, a registered class starts to require a base field. | ||
// Extending a {} struct requires breaking less code, so we should encourage it from the start. | ||
venial::Fields::Unit => Ok(vec![]), | ||
venial::Fields::Tuple(_) => bail!( | ||
&class.fields, | ||
"{derive_macro_name} is not supported for tuple structs", | ||
)?, | ||
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,42 @@ | ||||||||
/* | ||||||||
* Copyright (c) godot-rust; Bromeon and contributors. | ||||||||
* This Source Code Form is subject to the terms of the Mozilla Public | ||||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||||||
*/ | ||||||||
|
||||||||
// Note: group membership for properties in Godot is based on the order of their registration. | ||||||||
// All the properties belong to group or subgroup registered beforehand, identically as in gdscript. | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
// Initial implementation providing clap-like API with an explicit sorting | ||||||||
// & groups/subgroups declared for each field (`#[export(group = ..., subgroup = ...)]` | ||||||||
// can be found at: https://github.com/godot-rust/gdext/pull/1214. | ||||||||
|
||||||||
use crate::util::KvParser; | ||||||||
use crate::ParseResult; | ||||||||
use proc_macro2::Literal; | ||||||||
|
||||||||
/// Specifies group or subgroup which starts with a given field. | ||||||||
/// Group membership for properties in Godot is based on the order of their registration – | ||||||||
/// i.e. given field belongs to group declared beforehand (for example with some previous field). | ||||||||
pub struct FieldGroup { | ||||||||
pub(crate) name: Literal, | ||||||||
pub(crate) prefix: Literal, | ||||||||
} | ||||||||
|
||||||||
impl FieldGroup { | ||||||||
pub(crate) fn new_from_kv(parser: &mut KvParser) -> ParseResult<Self> { | ||||||||
// Groups without name and prefix are totally valid in godot and used | ||||||||
// to "break" out of groups and subgroups. See: | ||||||||
Comment on lines
+28
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
// https://docs.godotengine.org/en/4.4/classes/[email protected]#class-gdscript-annotation-export-group | ||||||||
let (name, prefix) = ( | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no point in using a tuple here, just keep things simple and declare two variables 🙂 |
||||||||
parser | ||||||||
.handle_literal("name", "String")? | ||||||||
.unwrap_or(Literal::string("")), | ||||||||
Comment on lines
+32
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. empty group/prefix is used to break out of groups in gdscript (works the same via GDExtension) https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html#grouping-exports. At some point we need to provide name anyway, so there is no point to use Option instead There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but "we need to support According to GDScript docs, the "Break out" is also not happening that often, so people can still specify |
||||||||
parser | ||||||||
.handle_literal("prefix", "String")? | ||||||||
.unwrap_or(Literal::string("")), | ||||||||
); | ||||||||
|
||||||||
Ok(Self { name, prefix }) | ||||||||
} | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,10 @@ | |
|
||
//! Parses the `#[var]` and `#[export]` attributes on fields. | ||
|
||
use crate::class::{Field, FieldVar, Fields, GetSet, GetterSetterImpl, UsageFlags}; | ||
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct}; | ||
use crate::class::data_models::fields::Fields; | ||
use crate::class::data_models::group_export::FieldGroup; | ||
use crate::class::{Field, FieldVar, GetSet, GetterSetterImpl, UsageFlags}; | ||
use crate::util::{format_funcs_collection_constant, format_funcs_collection_struct, ident}; | ||
use proc_macro2::{Ident, TokenStream}; | ||
use quote::quote; | ||
|
||
|
@@ -48,6 +50,8 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { | |
ty: field_type, | ||
var, | ||
export, | ||
group, | ||
subgroup, | ||
.. | ||
} = field; | ||
|
||
|
@@ -59,18 +63,17 @@ pub fn make_property_impl(class_name: &Ident, fields: &Fields) -> TokenStream { | |
} else { | ||
UsageFlags::InferredExport | ||
}; | ||
Some(FieldVar { | ||
FieldVar { | ||
usage_flags, | ||
..Default::default() | ||
}) | ||
} | ||
} | ||
|
||
(_, var) => var.clone(), | ||
(_, Some(var)) => var.clone(), | ||
_ => continue, | ||
}; | ||
|
||
let Some(var) = var else { | ||
continue; | ||
}; | ||
maybe_register_groups(group, subgroup, &mut export_tokens, class_name); | ||
|
||
let field_name = field_ident.to_string(); | ||
|
||
|
@@ -220,3 +223,38 @@ fn make_getter_setter( | |
|
||
quote! { #funcs_collection::#constant } | ||
} | ||
|
||
fn maybe_register_groups( | ||
group: &Option<FieldGroup>, | ||
subgroup: &Option<FieldGroup>, | ||
export_tokens: &mut Vec<TokenStream>, | ||
class_name: &Ident, | ||
) { | ||
Comment on lines
+227
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should have docs, the "maybe" isn't obvious. What does it depend on? Also, "register_groups" seems inaccurate -- this doesn't register anything, it just collects to tokens... |
||
export_tokens.push(make_group_registration( | ||
group, | ||
ident("register_group"), | ||
class_name, | ||
)); | ||
export_tokens.push(make_group_registration( | ||
subgroup, | ||
ident("register_subgroup"), | ||
class_name, | ||
)); | ||
} | ||
|
||
fn make_group_registration( | ||
group: &Option<FieldGroup>, | ||
register_fn: Ident, | ||
class_name: &Ident, | ||
) -> TokenStream { | ||
let Some(FieldGroup { name, prefix }) = group else { | ||
return TokenStream::new(); | ||
}; | ||
|
||
quote! { | ||
::godot::register::private::#register_fn::<#class_name>( | ||
#name, | ||
#prefix | ||
); | ||
} | ||
Comment on lines
+254
to
+259
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,11 @@ | |
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
use crate::class::data_models::fields::{named_fields, Fields}; | ||
use crate::class::data_models::group_export::FieldGroup; | ||
use crate::class::{ | ||
make_property_impl, make_virtual_callback, BeforeKind, Field, FieldCond, FieldDefault, | ||
FieldExport, FieldVar, Fields, SignatureInfo, | ||
FieldExport, FieldVar, SignatureInfo, | ||
}; | ||
use crate::util::{ | ||
bail, error, format_funcs_collection_struct, ident, path_ends_with_complex, | ||
|
@@ -33,9 +35,10 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> { | |
} | ||
|
||
let mut modifiers = Vec::new(); | ||
let named_fields = named_fields(class)?; | ||
let named_fields = named_fields(class, "#[derive(GodotClass)]")?; | ||
let mut struct_cfg = parse_struct_attributes(class)?; | ||
let mut fields = parse_fields(named_fields, struct_cfg.init_strategy)?; | ||
|
||
if struct_cfg.is_editor_plugin() { | ||
modifiers.push(quote! { with_editor_plugin }) | ||
} | ||
|
@@ -559,25 +562,6 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute | |
}) | ||
} | ||
|
||
/// Fetches data for all named fields for a struct. | ||
/// | ||
/// Errors if `class` is a tuple struct. | ||
fn named_fields(class: &venial::Struct) -> ParseResult<Vec<(venial::NamedField, Punct)>> { | ||
// This is separate from parse_fields to improve compile errors. The errors from here demand larger and more non-local changes from the API | ||
// user than those from parse_struct_attributes, so this must be run first. | ||
match &class.fields { | ||
// TODO disallow unit structs in the future | ||
// It often happens that over time, a registered class starts to require a base field. | ||
// Extending a {} struct requires breaking less code, so we should encourage it from the start. | ||
venial::Fields::Unit => Ok(vec![]), | ||
venial::Fields::Tuple(_) => bail!( | ||
&class.fields, | ||
"#[derive(GodotClass)] is not supported for tuple structs", | ||
)?, | ||
venial::Fields::Named(fields) => Ok(fields.fields.inner.clone()), | ||
} | ||
} | ||
|
||
/// Returns field names and 1 base field, if available. | ||
fn parse_fields( | ||
named_fields: Vec<(venial::NamedField, Punct)>, | ||
|
@@ -627,10 +611,10 @@ fn parse_fields( | |
} | ||
|
||
// Deprecated #[init(default = expr)] | ||
if let Some(default) = parser.handle_expr("default")? { | ||
if let Some((key, default)) = parser.handle_expr_with_key("default")? { | ||
if field.default_val.is_some() { | ||
return bail!( | ||
parser.span(), | ||
key, | ||
"Cannot use both `val` and `default` keys in #[init]; prefer using `val`" | ||
); | ||
Comment on lines
-630
to
619
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is unrelated to the PR, right? Is it a span improvement? Note that |
||
} | ||
|
@@ -683,6 +667,20 @@ fn parse_fields( | |
parser.finish()?; | ||
} | ||
|
||
// #[export_group(name = ..., prefix = ...)] | ||
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_group")? { | ||
let group = FieldGroup::new_from_kv(&mut parser)?; | ||
field.group = Some(group); | ||
parser.finish()?; | ||
} | ||
|
||
// #[export_subgroup(name = ..., prefix = ...)] | ||
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export_subgroup")? { | ||
let subgroup = FieldGroup::new_from_kv(&mut parser)?; | ||
field.subgroup = Some(subgroup); | ||
parser.finish()?; | ||
} | ||
|
||
// #[var] | ||
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "var")? { | ||
let var = FieldVar::new_from_kv(&mut parser)?; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making this function generic means it has to be monomorphized for every slightly different instantiation. Especially for internal APIs, I'd say compile times + simplicity are more important than minor convenience -> just use
&str
here.(There are probably other places across the code where this happens...)