1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
use Error;
use fmt;
use crateToGodot;
use crate;
/// Ergonomic catch-all error type for `#[func]` methods.
///
/// `strat::Unexpected` is intended for potential bugs that are **fixed during development**. They should not appear in Release builds. \
/// Do **not** use this for runtime errors that are expected (e.g. loading a savegame that can be corrupted).
///
/// When this error is returned, Godot logs it with [`godot_error!`]. The calling code [cannot reliably handle it][godot-proposal-7751].
/// This strategy is comparable to panics in Rust: the calling code is more ergonomic in the happy path, assuming that there are no errors.
/// In case that `Err(strat::Unexpected)` is returned, the calling code either aborts the function (debug varcall only) or continues with a
/// default value of the declared return type, which may introduce silent logic errors. GDScript has best-effort detection of such errors in Debug
/// mode; see [below](#behavior-on-the-call-site). The Rust object and its state remain valid after an error and can be used in subsequent calls.
///
/// This is currently the only [`ErrorToGodot`] impl that preserves type safety: for a Rust `#[func]` returning `Result<T, strat::Unexpected>`,
/// GDScript's static analysis sees the return type `T`. If you want to handle errors at runtime, choose another `ErrorToGodot` impl.
///
/// `strat::Unexpected` enables automatic conversions from other errors via `?` operator. This means you can mix different error types within the
/// same function body -- each one propagates via `?` and its message is forwarded to Godot. Use the [`func_bail!`] macro for early returns with
/// an error message.
///
/// [godot-proposal-7751]: https://github.com/godotengine/godot-proposals/discussions/7751
/// [`godot_error!`]: crate::global::godot_error
/// [`func_bail!`]: crate::meta::error::func_bail
///
/// # Example
/// ```no_run
/// use godot::prelude::*;
/// # #[derive(GodotClass)] #[class(init, base=Node3D)]
/// # struct PlayerCharacter { base: Base<Node3D>, config_map: std::collections::HashMap<String, String>, id: i32 }
///
/// #[godot_api]
/// impl PlayerCharacter {
/// // Verifies that required nodes are present in the developer-authored scene tree.
/// // Missing nodes are a scene setup bug, not an expected runtime condition.
/// #[func]
/// fn init_player(&mut self) -> Result<(), strat::Unexpected> {
/// // Node missing = scene setup bug.
/// let Some(hand) = self.base().try_get_node_as::<Node3D>("Skeleton3D/Hand") else {
/// func_bail!("Player {}: 3D model is missing a hand", self.id);
/// };
///
/// // HashMap loaded at startup; missing or unparseable values are a code bug.
/// let max_health = self.config_map
/// .get("max_health")
/// .ok_or("'max_health' key missing in config")? // &str
/// .parse::<i64>()?; // ParseIntError
///
/// // Initialize self with hand + max_health...
/// Ok(())
/// }
/// }
/// ```
///
/// This example uses [`Node::try_get_node_as()`][crate::classes::Node::try_get_node_as], the fallible counterpart to
/// [`Node::get_node_as()`][crate::classes::Node::get_node_as]. The latter panics on failure. From GDScript, the observable behavior is
/// the same, however the `try_*` + `?` approach allows more control over error propagation and works entirely without a Rust panic.
///
/// # Behavior on the call site
/// How a caller sees the `Err` variant of a returned `Result<T, strat::Unexpected>` depends:
///
/// - **Rust:** When calling a `#[func]` via `Object::try_call()` reflection, the caller will always see `Unexpected` errors manifest as `Err` in
/// the return type. This is the only way to reliably catch `Unexpected` errors, and works only because godot-rust maintains internal state.
/// - **GDScript:** If an `Unexpected` error is returned from Rust, the calling GDScript function will abort/fail if both conditions are true:
/// - the call uses _varcall_ (not _ptrcall_): invoked on an untyped `Variant` or using reflection via `Object.call()`.
/// - it runs in a Godot debug/editor build.
/// - Everything else returns Godot's default value for the type `T` (e.g. `null` for objects/variants, 0 for ints, etc.). This also applies to
/// other languages calling such a method (C#, godot-cpp, etc.).
// A small edge case seems to be a scene with a Rust class, that has a script attached. The GDScript _init() method then calls self.method(),
// which seems to behave like varcall rather than ptrcall, thus failing the call. Adding a `var x: MyClass = self; x.method()` however
// turns it into regular ptrcall, with continued execution and default value. Not yet reproduced in itest.
///
/// # `std::error::Error` and trait coherence
/// This type intentionally does **not** implement [`std::error::Error`] -- the reason is a Rust coherence constraint.
///
/// If `Unexpected` implemented `Error`, the blanket `impl<E: Into<Box<dyn Error + ...>>> From<E> for Unexpected` would conflict with the standard
/// library's `impl<T> From<T> for T`, because `Unexpected` would satisfy both `E = Unexpected` and `Unexpected: Into<Box<dyn Error>>`.
/// Omitting the `Error` impl sidesteps this conflict and is the same technique used by [`anyhow::Error`](https://docs.rs/anyhow).
///
/// Because `Unexpected` is typically the last error in a chain -- returned to Godot via `#[func]` -- the lack of direct error APIs is usually
/// not a big problem.
// Unexpected intentionally does NOT implement std::error::Error -- see the type-level docs for why.
/// Enables the `?` operator for any `E: Into<Box<dyn Error + Send + Sync + 'static>>`.
///
/// The following types satisfy this bound and can therefore be used with `?` or passed to [`Unexpected::new()`]:
///
/// | Source type | How it converts |
/// |---|---|
/// | Any `E: Error + Send + Sync + 'static` | Boxed directly; covers `std::io::Error`, `ParseIntError`, and any custom error type |
/// | `Box<dyn Error + Send + Sync + 'static>` | Used as-is |
/// | `String` | Wrapped in a message-only error |
/// | `&str` (any lifetime) | Copied to `String`, then wrapped -- the lifetime is not propagated |
// ----------------------------------------------------------------------------------------------------------------------------------------------
// ErrorToGodot impl -- unexpected mode
// ----------------------------------------------------------------------------------------------------------------------------------------------