Skip to content

match_class! fallback branch is now optional for () #1246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions godot-core/src/classes/match_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
/// The current implementation checks [`Gd::try_cast()`][crate::obj::Gd::try_cast] linearly with the number of branches.
/// This may change in the future.
///
/// Requires a fallback branch, even if all direct known classes are handled. The reason for this is that there may be other subclasses which
/// are not statically known by godot-rust (e.g. from a script or GDExtension). The fallback branch can either be `_` (discard object), or
/// `variable` to access the original object inside the fallback arm.
/// When none of the listed classes match, a _fallback branch_ acts as a catch-all and allows to retrieve the original `Gd` pointer.
/// If the type of the `match_class!` expression is `()`, you can omit the fallback branch. For all other types, it is required, even if all
/// direct subclasses are handled. The reason for this is that there may be other subclasses which are not statically known by godot-rust
/// (e.g. from a script or GDExtension).
///
/// The fallback branch can either be `_` (discard object), or `variable` to access the original object inside the fallback arm.
///
/// # Example
/// ```no_run
Expand Down Expand Up @@ -51,6 +54,7 @@
/// original => 0,
/// // Can also be used with mut:
/// // mut original => 0,
/// // If the match arms have type (), we can also omit the fallback branch.
/// };
///
/// // event_type is now 0, 1, 2, or 3
Expand All @@ -75,14 +79,6 @@ macro_rules! match_class {
#[doc(hidden)]
#[macro_export]
macro_rules! match_class_muncher {
/*
// If we want to support `variable @ _ => { ... }` syntax, use this branch first (to not match `_` as type).
($subject:ident, $var:ident @ _ => $block:expr $(,)?) => {{
let $var = $subject;
$block
}};
*/

// mut variable @ Class => { ... }.
($subject:ident, mut $var:ident @ $Ty:ty => $block:expr, $($rest:tt)*) => {{
match $subject.try_cast::<$Ty>() {
Expand Down Expand Up @@ -115,8 +111,9 @@ macro_rules! match_class_muncher {
$block
}};

// _ => { ... }.
($subject:ident, _ => $block:expr $(,)?) => {{
$block
// _ => { ... }
// or nothing, if fallback is absent and overall expression being ().
($subject:ident, $(_ => $block:expr $(,)?)?) => {{
$($block)?
}};
}
31 changes: 29 additions & 2 deletions itest/rust/src/engine_tests/match_class_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ fn match_class_control_flow() {

let mut broken = false;

#[allow(clippy::never_loop)]
#[expect(clippy::never_loop)]
for _i in 0..1 {
match_class! { obj.clone(),
let _: i32 = match_class! { obj.clone(),
_node @ Node => 1,
_res @ Resource => {
broken = true;
Expand All @@ -164,3 +164,30 @@ fn match_class_control_flow() {

assert!(broken, "break statement should have been executed");
}

#[itest]
fn match_class_unit_type() {
let obj: Gd<Object> = Object::new_alloc();
let to_free = obj.clone();
let mut val = 0;

match_class! { obj,
mut node @ Node2D => {
require_mut_node2d(&mut node);
val = 1;
},
node @ Node => {
require_node(&node);
val = 2;
},
// No need for _ branch since all branches return ().
}

assert_eq!(val, 0);
to_free.free();

// Special case: no branches at all. Also test unit type.
let _: () = match_class! { RefCounted::new_gd(),
// Nothing.
};
}
Loading