Skip to content

Commit 462b358

Browse files
committed
Add groups export.
- Check for string literal, instead of any expression; Return the key with proper span in the errors.
1 parent a68620c commit 462b358

File tree

4 files changed

+65
-35
lines changed

4 files changed

+65
-35
lines changed

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ pub struct Fields {
3131
pub errors: Vec<venial::Error>,
3232
}
3333

34-
impl Fields {
35-
/// Remove surrounding quotes to display declared "group name" in editor as `group name` instead of `"group name"`.
36-
/// Should be called after parsing all the fields to avoid unnecessary operations.
37-
pub fn format_groups(&mut self) {
38-
self.groups
39-
.iter_mut()
40-
.for_each(|g| *g = g.trim_matches('"').to_string());
41-
}
42-
}
43-
4434
/// Fetches data for all named fields for a struct.
4535
///
4636
/// Errors if `class` is a tuple struct.

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use crate::class::data_models::fields::Fields;
99
use crate::util::KvParser;
10+
use crate::ParseResult;
1011
use std::cmp::Ordering;
1112

1213
/// Points to index of a given group name in [Fields.groups](field@Fields::groups).
@@ -24,28 +25,42 @@ impl FieldGroup {
2425
expr: &'static str,
2526
parser: &mut KvParser,
2627
groups: &mut Vec<String>,
27-
) -> Option<GroupIdentifier> {
28-
let group = parser.handle_expr(expr).unwrap_or(None)?.to_string();
28+
) -> ParseResult<Option<GroupIdentifier>> {
29+
let Some(group) = parser.handle_string(expr)? else {
30+
return Ok(None);
31+
};
2932

3033
if let Some(group_index) = groups
3134
.iter()
3235
.position(|existing_group| existing_group == &group)
3336
{
34-
Some(group_index)
37+
Ok(Some(group_index))
3538
} else {
3639
groups.push(group);
37-
Some(groups.len() - 1)
40+
Ok(Some(groups.len() - 1))
3841
}
3942
}
4043

41-
pub(crate) fn new_from_kv(parser: &mut KvParser, groups: &mut Vec<String>) -> Self {
42-
Self {
43-
group_name_index: Self::parse_group("group", parser, groups),
44-
subgroup_name_index: Self::parse_group("subgroup", parser, groups),
45-
}
44+
pub(crate) fn new_from_kv(
45+
parser: &mut KvParser,
46+
groups: &mut Vec<String>,
47+
) -> ParseResult<Self> {
48+
Ok(Self {
49+
group_name_index: Self::parse_group("group", parser, groups)?,
50+
subgroup_name_index: Self::parse_group("subgroup", parser, groups)?,
51+
})
4652
}
4753
}
4854

55+
/// Remove surrounding quotes to display declared "group name" in editor as `group name` instead of `"group name"`.
56+
/// Should be called after parsing all the fields to avoid unnecessary operations.
57+
pub(crate) fn format_groups(groups: Vec<String>) -> Vec<String> {
58+
groups
59+
.into_iter()
60+
.map(|g| g.trim_matches('"').to_string())
61+
.collect()
62+
}
63+
4964
// ----------------------------------------------------------------------------------------------------------------------------------------------
5065
// Ordering
5166

@@ -189,6 +204,4 @@ pub(crate) fn sort_fields_by_group(fields: &mut Fields) {
189204
OrderingStage::Group,
190205
)
191206
});
192-
193-
fields.format_groups();
194207
}

godot-macros/src/class/derive_godot_class.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
use crate::class::data_models::fields::{named_fields, Fields};
9-
use crate::class::data_models::group_export::{sort_fields_by_group, FieldGroup};
9+
use crate::class::data_models::group_export::{format_groups, sort_fields_by_group, FieldGroup};
1010
use crate::class::{
1111
make_property_impl, make_virtual_callback, BeforeKind, Field, FieldCond, FieldDefault,
1212
FieldExport, FieldVar, SignatureInfo,
@@ -613,10 +613,10 @@ fn parse_fields(
613613
}
614614

615615
// Deprecated #[init(default = expr)]
616-
if let Some(default) = parser.handle_expr("default")? {
616+
if let Some((key, default)) = parser.handle_expr_with_key("default")? {
617617
if field.default_val.is_some() {
618618
return bail!(
619-
parser.span(),
619+
key,
620620
"Cannot use both `val` and `default` keys in #[init]; prefer using `val`"
621621
);
622622
}
@@ -666,7 +666,7 @@ fn parse_fields(
666666
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export")? {
667667
let export = FieldExport::new_from_kv(&mut parser)?;
668668
field.export = Some(export);
669-
let group = FieldGroup::new_from_kv(&mut parser, &mut groups);
669+
let group = FieldGroup::new_from_kv(&mut parser, &mut groups)?;
670670
field.group = Some(group);
671671
parser.finish()?;
672672
}
@@ -737,7 +737,7 @@ fn parse_fields(
737737
}
738738

739739
Ok(Fields {
740-
groups,
740+
groups: format_groups(groups),
741741
all_fields,
742742
base_field,
743743
deprecations,

godot-macros/src/util/kv_parser.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8+
use super::{bail, error, ident, is_punct, path_is_single, ListParser};
89
use crate::ParseResult;
910
use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree};
1011
use quote::ToTokens;
1112
use std::collections::HashMap;
1213

13-
use super::{bail, error, ident, is_punct, path_is_single, ListParser};
14-
1514
pub(crate) type KvMap = HashMap<Ident, Option<KvValue>>;
1615

1716
/// Struct to parse attributes like `#[attr(key, key2="value", key3=123)]` in a very user-friendly way.
@@ -156,36 +155,64 @@ impl KvParser {
156155
}
157156

158157
/// Handles an optional key that can occur with arbitrary tokens as the value.
159-
pub fn handle_expr(&mut self, key: &str) -> ParseResult<Option<TokenStream>> {
158+
///
159+
/// Returns both the key (with the correct span pointing to the attribute) and the value.
160+
/// [KvParser.span](field@KvParser::span) always points to the top of derive macro (`#[derive(GodotClass)]`).
161+
pub fn handle_expr_with_key(&mut self, key: &str) -> ParseResult<Option<(Ident, TokenStream)>> {
160162
match self.map.remove_entry(&ident(key)) {
161163
None => Ok(None),
162164
// The `key` that was removed from the map has the correct span.
163165
Some((key, value)) => match value {
164166
None => bail!(key, "expected `{key}` to be followed by `= expression`"),
165-
Some(value) => Ok(Some(value.expr()?)),
167+
Some(value) => Ok(Some((key, value.expr()?))),
166168
},
167169
}
168170
}
169171

170-
pub fn handle_usize(&mut self, key: &str) -> ParseResult<Option<usize>> {
171-
let Some(expr) = self.handle_expr(key)? else {
172+
/// Shortcut for [KvParser::handle_expr_with_key] which returns only the value.
173+
pub fn handle_expr(&mut self, key: &str) -> ParseResult<Option<TokenStream>> {
174+
match self.handle_expr_with_key(key)? {
175+
Some((_key, value)) => Ok(Some(value)),
176+
None => Ok(None),
177+
}
178+
}
179+
180+
pub fn handle_literal(
181+
&mut self,
182+
key: &str,
183+
expected_type: &str,
184+
) -> ParseResult<Option<Literal>> {
185+
let Some((key, expr)) = self.handle_expr_with_key(key)? else {
172186
return Ok(None);
173187
};
174188

175189
let mut tokens = expr.into_iter();
176190
let Some(TokenTree::Literal(lit)) = tokens.next() else {
177191
return bail!(
178192
key,
179-
"missing value for '{key}' (must be unsigned integer literal)"
193+
"missing value for '{key}' (must be {expected_type} literal)"
180194
);
181195
};
182196

183197
if let Some(surplus) = tokens.next() {
184198
return bail!(
185199
key,
186-
"value for '{key}' must be unsigned integer literal; found extra {surplus:?}"
200+
"value for '{key}' must be {expected_type} literal; found extra {surplus:?}"
187201
);
188202
}
203+
Ok(Some(lit))
204+
}
205+
206+
/// Handles a string literal (`att = "str"`).
207+
pub fn handle_string(&mut self, key: &str) -> ParseResult<Option<String>> {
208+
self.handle_literal(key, "String")
209+
.map(|possible_literal| possible_literal.map(|lit| lit.to_string()))
210+
}
211+
212+
pub fn handle_usize(&mut self, key: &str) -> ParseResult<Option<usize>> {
213+
let Some(lit) = self.handle_literal(key, "unsigned integer")? else {
214+
return Ok(None);
215+
};
189216

190217
let Ok(int) = lit.to_string().parse() else {
191218
return bail!(
@@ -199,7 +226,7 @@ impl KvParser {
199226

200227
#[allow(dead_code)]
201228
pub fn handle_bool(&mut self, key: &str) -> ParseResult<Option<bool>> {
202-
let Some(expr) = self.handle_expr(key)? else {
229+
let Some((key, expr)) = self.handle_expr_with_key(key)? else {
203230
return Ok(None);
204231
};
205232

0 commit comments

Comments
 (0)