Skip to content

Commit 05446f6

Browse files
authored
Merge pull request #850 from godot-rust/qol/require-tool-for-virtual-extensions
Validate that virtual extension classes require `#[class(tool)]`
2 parents 712e166 + 62bbf1d commit 05446f6

File tree

4 files changed

+43
-2
lines changed

4 files changed

+43
-2
lines changed

godot-core/src/obj/script.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,9 @@ use self::bounded_ptr_list::BoundedPtrList;
5353
/// use godot::extras::{IScriptExtension, ScriptInstance};
5454
///
5555
/// // 1) Define the script.
56+
/// // This needs #[class(tool)] since the script extension runs in the editor.
5657
/// #[derive(GodotClass)]
57-
/// #[class(init, base=ScriptExtension)]
58+
/// #[class(init, base=ScriptExtension, tool)]
5859
/// struct MyScript {
5960
/// base: Base<ScriptExtension>,
6061
/// // ... other fields

godot-core/src/registry/class.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) -> Result<(), ()> {
348348
/// Registers a class with given the dynamic type information `info`.
349349
fn register_class_raw(mut info: ClassRegistrationInfo) {
350350
// First register class...
351+
validate_class_constraints(&info);
351352

352353
let class_name = info.class_name;
353354
let parent_class_name = info
@@ -428,6 +429,10 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
428429
}
429430
}
430431

432+
fn validate_class_constraints(_class: &ClassRegistrationInfo) {
433+
// TODO: if we add builder API, the proc-macro checks in parse_struct_attributes() etc. should be duplicated here.
434+
}
435+
431436
fn unregister_class_raw(class: LoadedClass) {
432437
let class_name = class.name;
433438
out!("Unregister class: {class_name}");

godot-macros/src/class/derive_godot_class.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
368368
parser.finish()?;
369369
}
370370

371+
post_validate(&base_ty, is_tool)?;
372+
371373
Ok(ClassAttributes {
372374
base_ty,
373375
init_strategy,
@@ -557,3 +559,36 @@ fn handle_opposite_keys(
557559
),
558560
}
559561
}
562+
563+
/// Checks more logical combinations of attributes.
564+
fn post_validate(base_ty: &Ident, is_tool: bool) -> ParseResult<()> {
565+
// TODO: this should be delegated to either:
566+
// a) the type system: have a trait IsTool which is implemented when #[class(tool)] is set.
567+
// Then, for certain base classes, require a tool bound (e.g. generate method `fn type_check<T: IsTool>()`).
568+
// This would also allow moving the logic to godot-codegen.
569+
// b) a runtime check in class.rs > register_class_raw() and validate_class_constraints().
570+
571+
let is_extension = is_class_virtual_extension(&base_ty.to_string());
572+
if is_extension && !is_tool {
573+
return bail!(
574+
base_ty,
575+
"Base class `{}` is a virtual extension class, which runs in the editor and thus requires #[class(tool)].",
576+
base_ty
577+
);
578+
}
579+
580+
Ok(())
581+
}
582+
583+
/// Whether a class exists primarily for GDExtension to overload virtual methods.
584+
// See post_validate(). Should be moved to godot-codegen > special_cases.rs.
585+
fn is_class_virtual_extension(godot_class_name: &str) -> bool {
586+
// Heuristic: suffix, with some exceptions.
587+
// Generally, a rule might also be "there is another class without that suffix", however that doesn't apply to e.g. OpenXRAPIExtension.
588+
589+
match godot_class_name {
590+
"GDExtension" => false,
591+
592+
_ => godot_class_name.ends_with("Extension"),
593+
}
594+
}

itest/rust/src/builtin_tests/script/script_instance_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use godot::register::{godot_api, GodotClass};
1717
use godot::sys;
1818

1919
#[derive(GodotClass)]
20-
#[class(base = ScriptExtension, init)]
20+
#[class(base = ScriptExtension, init, tool)]
2121
struct TestScript {
2222
base: Base<ScriptExtension>,
2323
}

0 commit comments

Comments
 (0)