|
13 | 13 | ///
|
14 | 14 | /// Requires a fallback branch, even if all direct known classes are handled. The reason for this is that there may be other subclasses which
|
15 | 15 | /// are not statically known by godot-rust (e.g. from a script or GDExtension). The fallback branch can either be `_` (discard object), or
|
16 |
| -/// `variable @ _` to access the original object inside the fallback arm. |
| 16 | +/// `variable` to access the original object inside the fallback arm. |
17 | 17 | ///
|
18 | 18 | /// # Example
|
19 | 19 | /// ```no_run
|
|
26 | 26 | /// // Basic syntax.
|
27 | 27 | /// let event: Gd<InputEvent> = some_input();
|
28 | 28 | ///
|
29 |
| -/// let simple_dispatch: i32 = match_class!(event, { |
| 29 | +/// let simple_dispatch: i32 = match_class! { event, |
30 | 30 | /// button @ InputEventMouseButton => 1,
|
31 | 31 | /// motion @ InputEventMouseMotion => 2,
|
32 | 32 | /// action @ InputEventAction => 3,
|
33 | 33 | /// _ => 0, // Fallback.
|
34 |
| -/// }); |
| 34 | +/// }; |
35 | 35 | ///
|
36 | 36 | /// // More diverse dispatch patterns are also supported.
|
37 |
| -/// let fancy_dispatch: i32 = match_class!(some_input(), { |
| 37 | +/// let fancy_dispatch: i32 = match_class! { some_input(), |
38 | 38 | /// button @ InputEventMouseButton => 1,
|
39 | 39 | ///
|
40 | 40 | /// // Block syntax for multiple statements:
|
|
47 | 47 | /// action @ godot::classes::InputEventAction => 3,
|
48 | 48 | ///
|
49 | 49 | /// // Fallback with variable -- retrieves original Gd<InputEvent>.
|
50 |
| -/// // Equivalent to pattern `original @ InputEvent`. |
51 |
| -/// original @ _ => 0, |
52 |
| -/// }); |
| 50 | +/// original => 0, |
| 51 | +/// }; |
53 | 52 | ///
|
54 | 53 | /// // event_type is now 0, 1, 2, or 3
|
55 | 54 | /// ```
|
56 | 55 | ///
|
57 |
| -/// # Limitations |
58 |
| -/// The expression block is currently wrapped by a closure, so you cannot use control-flow statements like `?`, `return`, `continue`, `break`. |
| 56 | +/// # Expression and control flow |
| 57 | +/// The `match_class!` macro is an expression, as such it has a type. If that type is not `()`, you typically need to use the expression or |
| 58 | +/// end it with a semicolon. |
| 59 | +/// |
| 60 | +/// Control-flow statements like `?`, `return`, `continue`, `break` can be used within the match arms. |
59 | 61 | #[macro_export]
|
60 | 62 | // Note: annoyingly shows full implementation in docs. For workarounds, either move impl to a helper macro, or use something like
|
61 | 63 | // https://crates.io/crates/clean-macro-docs.
|
| 64 | +// Earlier syntax expected curly braces, i.e.: ($subject:expr, { $($tt:tt)* }) => {{ ... }}; |
62 | 65 | macro_rules! match_class {
|
63 |
| - // TT muncher approach: consume one arm at a time and recurse with the remaining tokens. |
| 66 | + ($subject:expr, $($tt:tt)*) => {{ |
| 67 | + let subject = $subject; |
| 68 | + $crate::match_class_muncher!(subject, $($tt)*) |
| 69 | + }}; |
| 70 | +} |
64 | 71 |
|
65 |
| - // Entry point: grab subject + ENTIRE list of arms as token-trees. |
66 |
| - ($subject:expr, { $($arms:tt)* }) => { |
67 |
| - (|| { |
68 |
| - let mut __match_subject = $subject; |
69 |
| - match_class!(@munch __match_subject; $($arms)*); |
70 |
| - // unreachable!("match_class hit end with no `_` fallback"); |
71 |
| - })() |
72 |
| - }; |
| 72 | +#[doc(hidden)] |
| 73 | +#[macro_export] |
| 74 | +macro_rules! match_class_muncher { |
| 75 | + /* |
| 76 | + // If we want to support `var @ _ => { ... }` syntax, use this branch first (to not match `_` as type). |
| 77 | + ($subject:ident, $var:ident @ _ => $block:expr $(,)?) => {{ |
| 78 | + let $var = $subject; |
| 79 | + $block |
| 80 | + }}; |
| 81 | + */ |
73 | 82 |
|
74 |
| - // ident @ Some::Path => expr, rest... |
75 |
| - (@munch $evt:ident; |
76 |
| - $var:ident @ $($class:ident)::+ => $body:expr, |
77 |
| - $($rest:tt)* |
78 |
| - ) => { |
79 |
| - // try the down‐cast |
80 |
| - $evt = match $evt.try_cast::< $($class)::* >() { |
81 |
| - Ok($var) => return $body, |
82 |
| - Err(e) => e, |
83 |
| - }; |
84 |
| - match_class!(@munch $evt; $($rest)*); |
85 |
| - }; |
| 83 | + ($subject:ident, $var:ident @ $Ty:ty => $block:expr, $($rest:tt)*) => {{ |
| 84 | + match $subject.try_cast::<$Ty>() { |
| 85 | + Ok($var) => $block, |
| 86 | + Err(__obj) => { |
| 87 | + $crate::match_class_muncher!(__obj, $($rest)*) |
| 88 | + } |
| 89 | + } |
| 90 | + }}; |
86 | 91 |
|
87 |
| - // foo @ _ => expr |
88 |
| - (@munch $evt:ident; |
89 |
| - $fallback_var:ident @ $pat:tt => $fallback:expr |
90 |
| - $(,)? |
91 |
| - ) => { |
92 |
| - // `$pat` here will only ever be `_` if no typed‐arm matched first, because `Some::Path` would hit the more specific rule above. |
93 |
| - let $fallback_var = $evt; |
94 |
| - return $fallback; |
95 |
| - }; |
| 92 | + ($subject:ident, $var:ident => $block:expr $(,)?) => {{ |
| 93 | + let $var = $subject; |
| 94 | + $block |
| 95 | + }}; |
96 | 96 |
|
97 |
| - // _ => expr |
98 |
| - (@munch $evt:ident; |
99 |
| - _ => $fallback:expr |
100 |
| - $(,)? |
101 |
| - ) => {{ |
102 |
| - return $fallback; |
| 97 | + ($subject:ident, _ => $block:expr $(,)?) => {{ |
| 98 | + $block |
103 | 99 | }};
|
104 | 100 | }
|
0 commit comments