Skip to content

Commit 60a82c0

Browse files
committed
Add #[hint] as an escape hatch for Base<T> and OnReady<T> inference
1 parent 1c143e2 commit 60a82c0

File tree

7 files changed

+131
-50
lines changed

7 files changed

+131
-50
lines changed

godot-core/src/obj/bounds.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(super) mod private {
6868
/// <strong>Never</strong> implement this trait manually.
6969
/// </div>
7070
///
71-
/// Most of the time, this trait is covered by [`#[derive(GodotClass)`](../bind/derive.GodotClass.html).
71+
/// Most of the time, this trait is covered by [`#[derive(GodotClass)]`](../register/derive.GodotClass.html).
7272
/// If you implement `GodotClass` manually, use the [`implement_godot_bounds!`][crate::implement_godot_bounds] macro.
7373
///
7474
/// There are two reasons to avoid a hand-written `impl Bounds`:

godot-core/src/obj/traits.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use godot_ffi as sys;
1717
/// Makes `T` eligible to be managed by Godot and stored in [`Gd<T>`][crate::obj::Gd] pointers.
1818
///
1919
/// The behavior of types implementing this trait is influenced by the associated types; check their documentation for information.
20+
///
21+
/// Normally, you don't need to implement this trait yourself; use [`#[derive(GodotClass)]`](../register/derive.GodotClass.html) instead.
2022
// Above intra-doc link to the derive-macro only works as HTML, not as symbol link.
2123
pub trait GodotClass: Bounds + 'static
2224
where

godot-macros/src/class/derive_godot_class.rs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,6 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
289289

290290
// Base<T> type inference
291291
if path_ends_with_complex(&field.ty, "Base") {
292-
if let Some(prev_base) = base_field.as_ref() {
293-
return bail!(
294-
field.name,
295-
"at most 1 field can have type Base<T>; previous is `{}`",
296-
prev_base.name
297-
);
298-
}
299-
300292
is_base = true;
301293
}
302294

@@ -332,24 +324,69 @@ fn parse_fields(class: &Struct) -> ParseResult<Fields> {
332324
parser.finish()?;
333325
}
334326

335-
// Exported or Rust-only fields
327+
// #[hint] to override type inference (must be at the end).
328+
if let Some(mut parser) = KvParser::parse(&named_field.attributes, "hint")? {
329+
if let Some(override_base) = handle_opposite_keys(&mut parser, "base")? {
330+
is_base = override_base;
331+
}
332+
333+
if let Some(override_onready) = handle_opposite_keys(&mut parser, "onready")? {
334+
field.is_onready = override_onready;
335+
}
336+
parser.finish()?;
337+
}
338+
339+
// Extra validation; eventually assign to base_fields or all_fields.
336340
if is_base {
337-
base_field = Some(field);
341+
if field.is_onready
342+
|| field.var.is_some()
343+
|| field.export.is_some()
344+
|| field.default.is_some()
345+
{
346+
return bail!(
347+
&named_field,
348+
"base field cannot have type `OnReady<T>` or attributes #[var], #[export] or #[init]"
349+
);
350+
}
351+
352+
if let Some(prev_base) = base_field.replace(field) {
353+
// Ensure at most one Base<T>.
354+
return bail!(
355+
// base_field.unwrap().name,
356+
named_field,
357+
"at most 1 field can have type Base<T>; previous is `{}`",
358+
prev_base.name
359+
);
360+
}
338361
} else {
339362
all_fields.push(field);
340363
}
341364
}
342365

343-
// TODO detect #[base] based on type instead of attribute
344-
// Edge cases (type aliases, user types with same name, ...) could be handled with #[hint(base)] or #[hint(no_base)].
345-
346366
Ok(Fields {
347367
all_fields,
348368
base_field,
349369
has_deprecated_base,
350370
})
351371
}
352372

373+
fn handle_opposite_keys(parser: &mut KvParser, key: &str) -> ParseResult<Option<bool>> {
374+
let antikey = format!("no_{}", key);
375+
376+
let is_key = parser.handle_alone(key)?;
377+
let is_no_key = parser.handle_alone(&antikey)?;
378+
379+
match (is_key, is_no_key) {
380+
(true, false) => Ok(Some(true)),
381+
(false, true) => Ok(Some(false)),
382+
(false, false) => Ok(None),
383+
(true, true) => bail!(
384+
parser.span(),
385+
"#[hint] attribute keys `{key}` and `{antikey}` are mutually exclusive",
386+
),
387+
}
388+
}
389+
353390
// ----------------------------------------------------------------------------------------------------------------------------------------------
354391
// General helpers
355392

godot-macros/src/lib.rs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ use crate::util::ident;
8181
/// your `struct`:
8282
///
8383
/// ```
84-
/// use godot::prelude::*;
85-
///
84+
/// # use godot::prelude::*;
8685
/// #[derive(GodotClass)]
8786
/// #[class(base = Node2D)]
8887
/// struct MyStruct {
@@ -94,8 +93,7 @@ use crate::util::ident;
9493
/// your object accordingly. You can access it through `self.base()` and `self.base_mut()` methods.
9594
///
9695
/// ```
97-
/// use godot::prelude::*;
98-
///
96+
/// # use godot::prelude::*;
9997
/// #[derive(GodotClass)]
10098
/// #[class(base = Node2D)]
10199
/// struct MyStruct {
@@ -116,8 +114,7 @@ use crate::util::ident;
116114
/// To create a property, you can use the `#[var]` annotation:
117115
///
118116
/// ```
119-
/// use godot::prelude::*;
120-
///
117+
/// # use godot::prelude::*;
121118
/// #[derive(GodotClass)]
122119
/// struct MyStruct {
123120
/// #[var]
@@ -134,8 +131,7 @@ use crate::util::ident;
134131
/// `#[export(get = ..., set = ...)]`:
135132
///
136133
/// ```
137-
/// use godot::prelude::*;
138-
///
134+
/// # use godot::prelude::*;
139135
/// #[derive(GodotClass)]
140136
/// struct MyStruct {
141137
/// #[var(get = get_my_field, set = set_my_field)]
@@ -161,8 +157,7 @@ use crate::util::ident;
161157
/// generated getter or setter in these cases anyway, use `get` or `set` without a value:
162158
///
163159
/// ```
164-
/// use godot::prelude::*;
165-
///
160+
/// # use godot::prelude::*;
166161
/// #[derive(GodotClass)]
167162
/// struct MyStruct {
168163
/// // Default getter, custom setter.
@@ -182,8 +177,7 @@ use crate::util::ident;
182177
/// For exporting properties to the editor, you can use the `#[export]` attribute:
183178
///
184179
/// ```
185-
/// use godot::prelude::*;
186-
///
180+
/// # use godot::prelude::*;
187181
/// #[derive(GodotClass)]
188182
/// struct MyStruct {
189183
/// #[export]
@@ -206,8 +200,7 @@ use crate::util::ident;
206200
/// As an example of some different export attributes:
207201
///
208202
/// ```
209-
/// use godot::prelude::*;
210-
///
203+
/// # use godot::prelude::*;
211204
/// #[derive(GodotClass)]
212205
/// struct MyStruct {
213206
/// // @export
@@ -249,8 +242,7 @@ use crate::util::ident;
249242
/// the export attributes.
250243
///
251244
/// ```
252-
/// use godot::prelude::*;
253-
///
245+
/// # use godot::prelude::*;
254246
/// const MAX_HEALTH: f64 = 100.0;
255247
///
256248
/// #[derive(GodotClass)]
@@ -267,8 +259,7 @@ use crate::util::ident;
267259
/// `hint`, `hint_string`, and `usage_flags` keys in the attribute:
268260
///
269261
/// ```
270-
/// use godot::prelude::*;
271-
///
262+
/// # use godot::prelude::*;
272263
/// #[derive(GodotClass)]
273264
/// struct MyStruct {
274265
/// // Treated as an enum with two values: "One" and "Two"
@@ -289,8 +280,7 @@ use crate::util::ident;
289280
/// It will be fundamentally reworked.
290281
///
291282
/// ```no_run
292-
/// use godot::prelude::*;
293-
///
283+
/// # use godot::prelude::*;
294284
/// #[derive(GodotClass)]
295285
/// struct MyClass {}
296286
///
@@ -311,7 +301,7 @@ use crate::util::ident;
311301
///
312302
/// This is very similar to [GDScript's `@tool` feature](https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html).
313303
///
314-
/// # Editor Plugins
304+
/// # Editor plugins
315305
///
316306
/// If you annotate a class with `#[class(editor_plugin)]`, it will be turned into an editor plugin. The
317307
/// class must then inherit from `EditorPlugin`, and an instance of that class will be automatically added
@@ -325,13 +315,13 @@ use crate::util::ident;
325315
/// This should usually be combined with `#[class(tool)]` so that the code you write will actually run in the
326316
/// editor.
327317
///
328-
/// # Class Renaming
318+
/// # Class renaming
329319
///
330320
/// You may want to have structs with the same name. With Rust, this is allowed using `mod`. However in GDScript,
331321
/// there are no modules, namespaces, or any such disambiguation. Therefore, you need to change the names before they
332322
/// can get to Godot. You can use the `rename` key while defining your `GodotClass` for this.
333323
///
334-
/// ```
324+
/// ```no_run
335325
/// mod animal {
336326
/// # use godot::prelude::*;
337327
/// #[derive(GodotClass)]
@@ -349,7 +339,7 @@ use crate::util::ident;
349339
///
350340
/// These classes will appear in the Godot editor and GDScript as "AnimalToad" or "NpcToad".
351341
///
352-
/// # Hiding Classes
342+
/// # Hiding classes
353343
///
354344
/// If you want to register a class with Godot, but not have it show up in the editor then you can use `#[class(hide)]`.
355345
///
@@ -362,6 +352,35 @@ use crate::util::ident;
362352
///
363353
/// Even though this class is a `Node` and it has an init function, it still won't show up in the editor as a node you can add to a scene
364354
/// because we have added a `hide` key to the class. This will also prevent it from showing up in documentation.
355+
///
356+
/// # Fine-grained inference hints
357+
///
358+
/// The derive macro is relatively smart about recognizing `Base<T>` and `OnReady<T>` types, and works also if those are qualified.
359+
///
360+
/// However, there may be situations where you need to help it out -- for example, if you have a type alias for `Base<T>`, or use an unrelated
361+
/// `my_module::Base<T>` with a different meaning.
362+
///
363+
/// In this case, you can manually override the behavior with the `#[hint]` attribute. It takes multiple standalone keys:
364+
/// - `base` and `no_base`
365+
/// - `onready` and `no_onready`
366+
///
367+
/// ```no_run
368+
/// use godot::engine::Node;
369+
///
370+
/// // There's no reason to do this, but for the sake of example:
371+
/// type Super<T> = godot::obj::Base<T>;
372+
/// type Base<T> = godot::obj::Gd<T>;
373+
///
374+
/// #[derive(godot::register::GodotClass)]
375+
/// #[class(base = Node)]
376+
/// struct MyStruct {
377+
/// #[hint(base)]
378+
/// base: Super<Node>,
379+
///
380+
/// #[hint(no_base)]
381+
/// unbase: Base<Node>,
382+
/// }
383+
/// ```
365384
#[proc_macro_derive(GodotClass, attributes(class, base, hint, var, export, init, signal))]
366385
pub fn derive_godot_class(input: TokenStream) -> TokenStream {
367386
translate(input, class::derive_godot_class)
@@ -486,7 +505,7 @@ pub fn derive_to_godot(input: TokenStream) -> TokenStream {
486505
translate(input, derive::derive_to_godot)
487506
}
488507

489-
/// Derive macro for [`FromGodot`](../builtin/meta/trait.FromVariant.html) on structs or enums.
508+
/// Derive macro for [`FromGodot`](../builtin/meta/trait.FromGodot.html) on structs or enums.
490509
///
491510
/// # Example
492511
///
@@ -520,7 +539,7 @@ pub fn derive_from_godot(input: TokenStream) -> TokenStream {
520539
translate(input, derive::derive_from_godot)
521540
}
522541

523-
/// Derive macro for [`Var`](../bind/property/trait.Var.html) on enums.
542+
/// Derive macro for [`Var`](../register/property/trait.Var.html) on enums.
524543
///
525544
/// Currently has some tight requirements which are expected to be softened as implementation expands:
526545
/// - Only works for enums, structs aren't supported by this derive macro at the moment.
@@ -560,7 +579,7 @@ pub fn derive_property(input: TokenStream) -> TokenStream {
560579
translate(input, derive::derive_var)
561580
}
562581

563-
/// Derive macro for [`Export`](../bind/property/trait.Export.html) on enums.
582+
/// Derive macro for [`Export`](../register/property/trait.Export.html) on enums.
564583
///
565584
/// Currently has some tight requirements which are expected to be softened as implementation expands, see requirements for [`Var`].
566585
#[proc_macro_derive(Export)]

itest/rust/src/object_tests/base_test.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,24 @@ fn base_gd_self() {
107107

108108
// ----------------------------------------------------------------------------------------------------------------------------------------------
109109

110-
#[derive(GodotClass)]
111-
#[class(init, base=Node2D)]
112-
struct Based {
113-
base: Base<Node2D>,
114-
115-
i: i32,
110+
use renamed_bases::Based;
111+
mod renamed_bases {
112+
use super::{GodotClass, Node2D};
113+
114+
// Test #[hint].
115+
type Super<T> = super::Base<T>;
116+
type Base<T> = T;
117+
118+
#[derive(GodotClass)]
119+
#[class(init, base = Node2D)]
120+
pub struct Based {
121+
#[hint(base)]
122+
pub base: Super<Node2D>, // de-facto: Base<Node2D>.
123+
124+
// This can coexist because it's not really a base.
125+
#[hint(no_base)]
126+
pub i: Base<i32>, // de-facto: i32
127+
}
116128
}
117129

118130
impl Based {

itest/rust/src/object_tests/onready_test.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ fn onready_lifecycle_with_impl_without_ready() {
112112

113113
*obj.auto = 77;
114114
assert_eq!(*obj.auto, 77);
115+
116+
// Test #[hint(no_onready)]: we can still initialize it (would panic if already auto-initialized).
117+
godot::private::auto_init(&mut obj.nothing);
115118
}
116119

117120
obj.free();
@@ -199,20 +202,27 @@ impl OnReadyWithoutImpl {
199202

200203
// ----------------------------------------------------------------------------------------------------------------------------------------------
201204

205+
type Ordy<T> = OnReady<T>;
206+
202207
// Class that has a #[godot_api] impl, but does not override ready. Used to test whether variables are still initialized.
203208
#[derive(GodotClass)]
204209
#[class(base=Node)]
205210
struct OnReadyWithImplWithoutReady {
206-
auto: OnReady<i32>,
211+
// Test also #[hint] at the same time.
212+
#[hint(onready)]
213+
auto: Ordy<i32>,
207214
// No manual one, since those cannot be initialized without a ready() override.
208215
// (Technically they _can_ at the moment, but in the future we might ensure initialization after ready, so this is not a supported workflow).
216+
#[hint(no_onready)]
217+
nothing: OnReady<i32>,
209218
}
210219

211220
// Rust-only impl, no proc macros.
212221
impl OnReadyWithImplWithoutReady {
213222
fn create() -> Gd<OnReadyWithImplWithoutReady> {
214223
let obj = Self {
215-
auto: OnReady::new(|| 66),
224+
auto: Ordy::new(|| 66),
225+
nothing: Ordy::new(|| -111),
216226
};
217227

218228
Gd::from_object(obj)

itest/rust/src/object_tests/property_test.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ pub struct DeriveExport {
326326
#[export]
327327
pub foo: TestEnum,
328328

329-
pub base: Base<RefCounted>,
329+
// Tests also qualified base path (type inference of Base<T> without #[hint]).
330+
pub base: godot::obj::Base<RefCounted>,
330331
}
331332

332333
#[godot_api]

0 commit comments

Comments
 (0)