Skip to content

Commit 46afcc7

Browse files
committed
add alternative syntax for manual property registration
1 parent 52709eb commit 46afcc7

File tree

5 files changed

+89
-4
lines changed

5 files changed

+89
-4
lines changed

godot-macros/src/class/derive_godot_class.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,44 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
106106
is_tool = true;
107107
}
108108

109+
if let Some(mut list_parser) = parser.handle_array("properties")? {
110+
while let Some(inner_list_parser) = list_parser.try_next_list(Delimiter::Parenthesis)? {
111+
let mut parser =
112+
KvParser::parse_from_list_parser(inner_list_parser, Delimiter::Parenthesis)?;
113+
114+
let name = parser.handle_expr_required("name")?.to_string();
115+
let ty = parser.handle_expr_required("type")?;
116+
117+
let field_var = FieldVar::new_from_kv(&mut parser)?;
118+
if field_var.getter.is_omitted() && field_var.setter.is_omitted() {
119+
bail!(
120+
parser.span(),
121+
"#[properties] item must define at least 1 getter or setter"
122+
)?;
123+
}
124+
if field_var.getter.is_generated() || field_var.setter.is_generated() {
125+
bail!(
126+
parser.span(),
127+
"#[properties] item does not support generated getters and setters"
128+
)?;
129+
}
130+
131+
let mut field = Field::new(
132+
ident(name.as_str()),
133+
venial::TyExpr {
134+
tokens: vec![TokenTree::Group(Group::new(Delimiter::None, ty))],
135+
},
136+
);
137+
field.var = Some(field_var);
138+
139+
standlone_properties.push(field);
140+
141+
parser.finish()?;
142+
}
143+
144+
list_parser.finish()?;
145+
}
146+
109147
parser.finish()?;
110148
}
111149

godot-macros/src/util/kv_parser.rs

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

77
use crate::ParseResult;
8-
use proc_macro2::{Delimiter, Ident, Spacing, Span, TokenStream, TokenTree};
8+
use proc_macro2::{Delimiter, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
99
use quote::ToTokens;
1010
use std::collections::HashMap;
1111
use venial::Attribute;
@@ -77,6 +77,28 @@ impl KvParser {
7777
Ok(found_attrs)
7878
}
7979

80+
pub fn parse_from_list_parser(parser: ListParser, delimiter: Delimiter) -> ParseResult<Self> {
81+
let span = parser.span_close;
82+
let tokens = parser
83+
.lists
84+
.into_iter()
85+
.flat_map(|v| {
86+
let mut tokens = v.into_tokens();
87+
tokens.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
88+
89+
tokens
90+
})
91+
.collect::<Vec<TokenTree>>();
92+
93+
Ok(Self {
94+
span,
95+
map: ParserState::parse(
96+
"nested list".to_string(),
97+
&venial::AttributeValue::Group(venial::GroupSpan { delimiter, span }, tokens),
98+
)?,
99+
})
100+
}
101+
80102
pub fn span(&self) -> Span {
81103
self.span
82104
}
@@ -333,7 +355,11 @@ impl<'a> ParserState<'a> {
333355
} else {
334356
"".to_owned()
335357
};
336-
return bail!(cur, "expected identifier{parens_hint}");
358+
let attr_name = self.attr_name;
359+
let tokens = self.tokens;
360+
let prev = self.prev;
361+
let cur = self.cur;
362+
return bail!(cur, "expected identifier{parens_hint} attr {attr_name} tokens {tokens:?} prev {prev:?} cur {cur:?}");
337363
}
338364
}
339365
}

godot-macros/src/util/list_parser.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use crate::ParseResult;
1515
/// Parses a list of tokens as an ordered list of values. Unlike [`KvParser`] which treats the tokens as a
1616
/// set of values.
1717
pub struct ListParser {
18-
lists: VecDeque<KvValue>,
18+
pub(super) lists: VecDeque<KvValue>,
1919
/// The last span of the list, usually a closing parenthesis.
20-
span_close: Span,
20+
pub(super) span_close: Span,
2121
}
2222

2323
impl ListParser {
@@ -194,6 +194,18 @@ impl ListParser {
194194
}
195195
}
196196

197+
/// Takes the next list element of the list.
198+
///
199+
/// # Example
200+
/// `((name = foo), (name = boo))` will yield `(name = foo)`
201+
pub(crate) fn try_next_list(&mut self, delimiter: Delimiter) -> ParseResult<Option<Self>> {
202+
let Some(kv) = self.pop_next() else {
203+
return Ok(None);
204+
};
205+
206+
Ok(Some(ListParser::new_from_tree(kv.single()?, delimiter)?))
207+
}
208+
197209
/// Ensure all values have been consumed.
198210
pub fn finish(&mut self) -> ParseResult<()> {
199211
if let Some(kv) = self.pop_next() {

itest/godot/ManualFfiTests.gd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,6 @@ func test_standalone_property():
323323
assert_eq(standalone_property.my_int, 10)
324324
assert_eq(standalone_property.readonly_int, 10)
325325
assert_eq(standalone_property.int_array, [10])
326+
327+
assert_eq(standalone_property.first_int, 10)
328+
assert_eq(standalone_property.second_int, 10)

itest/rust/src/object_tests/property_test.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,12 @@ fn derive_export() {
368368
}
369369

370370
#[derive(GodotClass)]
371+
#[class(
372+
properties = [
373+
(name = first_int, type = i32, get = get_integer),
374+
(name = second_int, type = i32, get = get_integer)
375+
]
376+
)]
371377
#[property(name = my_int, type = i32, get = get_integer, set = set_integer)]
372378
#[property(name = readonly_int, type = i32, get = get_integer)]
373379
#[property(name = int_array, type = Array<i32>, get = get_integer_as_array, set = set_integer_from_array_front)]

0 commit comments

Comments
 (0)