Skip to content

Commit c6ebd3d

Browse files
bors[bot]ttencate
andauthored
Merge #205
205: Implement #[init(default = ...)] annotation r=Bromeon a=ttencate Fixes #199 Co-authored-by: Thomas ten Cate <[email protected]>
2 parents c1c2d60 + 2a201f5 commit c6ebd3d

File tree

5 files changed

+115
-33
lines changed

5 files changed

+115
-33
lines changed

godot-macros/src/derive_godot_class.rs

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,10 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
8989

9090
/// Returns field names and 1 base field, if available
9191
fn parse_fields(class: &Struct) -> ParseResult<Fields> {
92-
let mut all_field_names = vec![];
93-
let mut exported_fields = vec![];
92+
let mut all_fields = vec![];
9493
let mut base_field = Option::<Field>::None;
9594

96-
let fields: Vec<(NamedField, Punct)> = match &class.fields {
95+
let named_fields: Vec<(NamedField, Punct)> = match &class.fields {
9796
StructFields::Unit => {
9897
vec![]
9998
}
@@ -105,42 +104,50 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
105104
};
106105

107106
// Attributes on struct fields
108-
for (field, _punct) in fields {
107+
for (named_field, _punct) in named_fields {
109108
let mut is_base = false;
109+
let mut field = Field::new(&named_field);
110110

111111
// #[base]
112-
if let Some(parser) = KvParser::parse(&field.attributes, "base")? {
113-
if let Some(prev_base) = base_field {
112+
if let Some(parser) = KvParser::parse(&named_field.attributes, "base")? {
113+
if let Some(prev_base) = base_field.as_ref() {
114114
bail(
115115
format!(
116-
"#[base] allowed for at most 1 field, already applied to '{}'",
116+
"#[base] allowed for at most 1 field, already applied to `{}`",
117117
prev_base.name
118118
),
119119
parser.span(),
120120
)?;
121121
}
122122
is_base = true;
123-
base_field = Some(Field::new(&field));
123+
parser.finish()?;
124+
}
125+
126+
// #[init]
127+
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "init")? {
128+
let default = parser.handle_expr("default")?;
129+
field.default = default;
124130
parser.finish()?;
125131
}
126132

127133
// #[export]
128-
if let Some(mut parser) = KvParser::parse(&field.attributes, "export")? {
129-
let exported_field = ExportedField::new_from_kv(Field::new(&field), &mut parser)?;
130-
exported_fields.push(exported_field);
134+
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "export")? {
135+
let export = FieldExport::new_from_kv(&mut parser)?;
136+
field.export = Some(export);
131137
parser.finish()?;
132138
}
133139

134140
// Exported or Rust-only fields
135-
if !is_base {
136-
all_field_names.push(field.name.clone())
141+
if is_base {
142+
base_field = Some(field);
143+
} else {
144+
all_fields.push(field);
137145
}
138146
}
139147

140148
Ok(Fields {
141-
all_field_names,
149+
all_fields,
142150
base_field,
143-
exported_fields,
144151
})
145152
}
146153

@@ -153,27 +160,31 @@ struct ClassAttributes {
153160
}
154161

155162
struct Fields {
156-
all_field_names: Vec<Ident>,
163+
/// All fields except `base_field`.
164+
all_fields: Vec<Field>,
165+
/// The field annotated with `#[base]`.
157166
base_field: Option<Field>,
158-
exported_fields: Vec<ExportedField>,
159167
}
160168

161169
struct Field {
162170
name: Ident,
163171
ty: TyExpr,
172+
default: Option<TokenStream>,
173+
export: Option<FieldExport>,
164174
}
165175

166176
impl Field {
167177
fn new(field: &NamedField) -> Self {
168178
Self {
169179
name: field.name.clone(),
170180
ty: field.ty.clone(),
181+
default: None,
182+
export: None,
171183
}
172184
}
173185
}
174186

175-
struct ExportedField {
176-
field: Field,
187+
struct FieldExport {
177188
getter: GetterSetter,
178189
setter: GetterSetter,
179190
hint: Option<ExportHint>,
@@ -219,8 +230,8 @@ impl ExportHint {
219230
}
220231
}
221232

222-
impl ExportedField {
223-
pub fn new_from_kv(field: Field, parser: &mut KvParser) -> ParseResult<ExportedField> {
233+
impl FieldExport {
234+
pub fn new_from_kv(parser: &mut KvParser) -> ParseResult<FieldExport> {
224235
let mut getter = GetterSetter::parse(parser, "get")?;
225236
let mut setter = GetterSetter::parse(parser, "set")?;
226237
if getter == GetterSetter::Omitted && setter == GetterSetter::Omitted {
@@ -238,8 +249,7 @@ impl ExportedField {
238249
})
239250
.transpose()?;
240251

241-
Ok(ExportedField {
242-
field,
252+
Ok(FieldExport {
243253
getter,
244254
setter,
245255
hint,
@@ -254,8 +264,13 @@ fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
254264
TokenStream::new()
255265
};
256266

257-
let rest_init = fields.all_field_names.into_iter().map(|field| {
258-
quote! { #field: std::default::Default::default(), }
267+
let rest_init = fields.all_fields.into_iter().map(|field| {
268+
let field_name = field.name;
269+
let value_expr = match field.default {
270+
None => quote!(::std::default::Default::default()),
271+
Some(default) => default,
272+
};
273+
quote! { #field_name: #value_expr, }
259274
});
260275

261276
quote! {
@@ -295,20 +310,21 @@ fn make_deref_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
295310

296311
fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
297312
let mut getter_setter_impls = Vec::new();
298-
let mut export_tokens = Vec::with_capacity(fields.exported_fields.len());
313+
let mut export_tokens = Vec::new();
299314

300-
for exported_field in &fields.exported_fields {
301-
let field_name = exported_field.field.name.to_string();
315+
for field in &fields.all_fields {
316+
let Some(export) = &field.export else { continue; };
317+
let field_name = field.name.to_string();
302318
let field_ident = ident(&field_name);
303-
let field_type = exported_field.field.ty.clone();
319+
let field_type = field.ty.clone();
304320

305321
let ExportHint {
306322
hint_type,
307323
description,
308-
} = exported_field.hint.clone().unwrap_or_else(ExportHint::none);
324+
} = export.hint.clone().unwrap_or_else(ExportHint::none);
309325

310326
let getter_name;
311-
match &exported_field.getter {
327+
match &export.getter {
312328
GetterSetter::Omitted => {
313329
getter_name = "".to_owned();
314330
}
@@ -334,7 +350,7 @@ fn make_exports_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
334350
}
335351

336352
let setter_name;
337-
match &exported_field.setter {
353+
match &export.setter {
338354
GetterSetter::Omitted => {
339355
setter_name = "".to_owned();
340356
}

godot-macros/src/lib.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,38 @@ mod util;
110110
/// }
111111
/// ```
112112
///
113+
/// The generated `init` function will initialize each struct field (except the field annotated
114+
/// with `#[base]`, if any) using `Default::default()`. To assign some other value, annotate the
115+
/// field with `#[init(default = ...)]`:
116+
///
117+
/// ```
118+
/// # use godot_macros::GodotClass;
119+
/// #[derive(GodotClass)]
120+
/// #[class(init)]
121+
/// struct MyStruct {
122+
/// #[init(default = 42)]
123+
/// my_field: i64
124+
/// }
125+
/// ```
126+
///
127+
/// The given value can be any Rust expression that can be evaluated in the scope where you write
128+
/// the attribute. However, due to limitations in the parser, some complex expressions must be
129+
/// surrounded by parentheses. This is the case if the expression includes a `,` that is _not_
130+
/// inside any pair of `(...)`, `[...]` or `{...}` (even if it is, for example, inside `<...>` or
131+
/// `|...|`). A contrived example:
132+
///
133+
/// ```
134+
/// # use godot_macros::GodotClass;
135+
/// # use std::collections::HashMap;
136+
/// # #[derive(GodotClass)]
137+
/// # #[class(init)]
138+
/// # struct MyStruct {
139+
/// #[init(default = (HashMap::<i64, i64>::new()))]
140+
/// // ^ parentheses needed due to this comma
141+
/// # my_field: HashMap<i64, i64>,
142+
/// # }
143+
/// ```
144+
///
113145
/// # Inheritance
114146
///
115147
/// Unlike C++, Rust doesn't really have inheritance, but the GDExtension API lets us "inherit"
@@ -223,7 +255,7 @@ mod util;
223255
///
224256
/// The `#[signal]` attribute is accepted, but not yet implemented. See [issue
225257
/// #8](https://github.com/godot-rust/gdext/issues/8).
226-
#[proc_macro_derive(GodotClass, attributes(class, export, base, signal))]
258+
#[proc_macro_derive(GodotClass, attributes(class, base, export, init, signal))]
227259
pub fn derive_native_class(input: TokenStream) -> TokenStream {
228260
translate(input, derive_godot_class::transform)
229261
}

itest/godot/ManualFfiTests.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ func test_missing_init():
1818

1919
print("[GD] WithoutInit is: ", instance)
2020

21+
func test_init_defaults():
22+
var obj = WithInitDefaults.new()
23+
24+
assert_eq(obj.default_int, 0)
25+
assert_eq(obj.literal_int, 42)
26+
assert_eq(obj.expr_int, -42)
27+
2128
func test_to_string():
2229
var ffi = VirtualMethodTest.new()
2330

itest/rust/src/init_test.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use godot::prelude::*;
8+
9+
#[derive(GodotClass)]
10+
#[class(init)]
11+
struct WithInitDefaults {
12+
#[export(get)]
13+
default_int: i64,
14+
15+
#[export(get)]
16+
#[init(default = 42)]
17+
literal_int: i64,
18+
19+
#[export(get)]
20+
#[init(default = -42)]
21+
expr_int: i64,
22+
}
23+
24+
// TODO Remove once https://github.com/godot-rust/gdext/issues/187 is fixed
25+
#[godot_api]
26+
impl WithInitDefaults {}

itest/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod dictionary_test;
1919
mod enum_test;
2020
mod export_test;
2121
mod gdscript_ffi_test;
22+
mod init_test;
2223
mod node_test;
2324
mod object_test;
2425
mod packed_array_test;

0 commit comments

Comments
 (0)