godot-core 0.5.1

Internal crate used by godot-rust
Documentation
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
/*
 * 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 std::error::Error;
use std::fmt;

use godot_ffi::join_debug;

use crate::builtin::{Variant, VariantType};
use crate::meta::error::{ConvertError, ErasedConvertError};
use crate::meta::{CallContext, ToGodot};
use crate::private::PanicPayload;
use crate::sys;

/// Result type for function calls that can fail.
pub(crate) type CallResult<R> = Result<R, CallError>;

/// Error capable of representing failed function calls.
///
/// This type is returned from _varcall_ functions in the Godot API that begin with `try_` prefixes,
/// e.g. [`Object::try_call()`](crate::classes::Object::try_call) or [`Node::try_rpc()`](crate::classes::Node::try_rpc).
/// _Varcall_ refers to the "variant call" calling convention, meaning that arguments and return values are passed as `Variant` (as opposed
/// to _ptrcall_, which passes direct pointers to Rust objects).
///
/// Allows to inspect the involved class and method via `class_id()` and `method_name()`. Implements the `std::error::Error` trait, so
/// it comes with `Display` and `Error::source()` APIs.
///
/// # Possible error causes
/// Several reasons can cause a function call to fail. The reason is described in the `Display` impl.
///
/// - **Invalid method**: The method does not exist on the object.
/// - **Failed argument conversion**: The arguments passed to the method cannot be converted to the declared parameter types.
/// - **Failed return value conversion**: The returned `Variant` of a dynamic method cannot be converted to the expected return type.
/// - **Too many or too few arguments**: The number of arguments passed to the method does not match the number of parameters.
/// - **User panic**: A Rust method caused a panic.
///
/// # Chained errors
/// Let's say you have this code, and you want to call the method dynamically with `Object::try_call()`.
///
/// Then, the immediate `CallError` will refer to the `Object::try_call` method, and its source will refer to `MyClass::my_method`
/// (the actual method that failed).
/// ```no_run
/// use godot::prelude::*;
/// use std::error::Error;
/// # use godot_core::meta::error::{CallError, ConvertError};
/// #[derive(GodotClass)]
/// # #[class(init)]
/// struct MyClass;
///
/// #[godot_api]
/// impl MyClass {
///     #[func]
///     fn my_method(&self, arg: i64) {}
/// }
///
/// fn some_method() {
///     let mut obj: Gd<MyClass> = MyClass::new_gd();
///
///     // Dynamic call. Note: forgot to pass the argument.
///     let result: Result<Variant, CallError> = obj.try_call("my_method", &[]);
///
///     // Get immediate and original errors.
///     // Note that source() can be None or of type ConvertError.
///     let outer: CallError = result.unwrap_err();
///     let inner: &CallError = outer.source().unwrap().downcast_ref::<CallError>().unwrap();
///
///     // Immediate error: try_call() internally invokes Object::call().
///     assert_eq!(outer.class_name(), Some("Object"));
///     assert_eq!(outer.method_name(), "call");
///
///     // Original error: my_method() failed.
///     assert_eq!(inner.class_name(), Some("MyClass"));
///     assert_eq!(inner.method_name(), "my_method");
/// }
pub struct CallError {
    // Boxed since the original struct is >= 176 bytes, making Result<..., CallError> very large.
    b: Box<InnerCallError>,
}

/// Inner struct. All functionality on outer `impl`.
#[derive(Debug)]
struct InnerCallError {
    class_name: String,
    function_name: String,
    call_expr: String,
    reason: String,
    source: Option<SourceError>,
    /// Whether this error was caused by a Rust panic. If so, the panic hook already printed it.
    caused_by_panic: bool,
}

impl CallError {
    // Naming:
    // - check_* means possible failure -- Result<(), Self> is returned.
    // - failed_* means definitive failure -- Self is returned.

    /// Name of the class/builtin whose method failed. **Not** the dynamic type.
    ///
    /// Returns `None` if this is a utility function (without a surrounding class/builtin).
    ///
    /// This is the static and not the dynamic type. For example, if you invoke `call()` on a `Gd<Node>`, you are effectively invoking
    /// `Object::call()` (through `DerefMut`), and the class name will be `Object`.
    pub fn class_name(&self) -> Option<&str> {
        if self.b.class_name.is_empty() {
            None
        } else {
            Some(&self.b.class_name)
        }
    }

    /// Name of the function or method that failed.
    pub fn method_name(&self) -> &str {
        &self.b.function_name
    }

    // ------------------------------------------------------------------------------------------------------------------------------------------
    // Constructors returning Result<(), Self>; possible failure

    /// Checks whether number of arguments matches the number of parameters.
    pub(crate) fn check_arg_count(
        call_ctx: &CallContext,
        arg_count: usize,           // Arguments passed by the caller.
        default_value_count: usize, // Fallback/default values, *not* arguments.
        param_count: usize,         // Parameters declared by the function.
    ) -> Result<(), Self> {
        // Valid if both:
        // - Provided args + available defaults (fallbacks) are enough to fill all parameters.
        // - Provided args don't exceed parameter count.
        if arg_count + default_value_count >= param_count && arg_count <= param_count {
            return Ok(());
        }

        let call_error = Self::failed_param_count(call_ctx, arg_count, param_count);
        Err(call_error)
    }

    /// Checks the Godot side of a varcall (low-level `sys::GDExtensionCallError`).
    pub(crate) fn check_out_varcall<T: ToGodot>(
        call_ctx: &CallContext,
        err: sys::GDExtensionCallError,
        explicit_args: &[T],
        varargs: &[Variant],
    ) -> Result<(), Self> {
        // Always drain stale thread-local errors (even on success), so that entries left over from previous `#[func]` failures
        // (invoked by GDScript, not via Rust `try_call`) cannot be misattributed to a later call.
        let source_error = crate::private::call_error_take();

        if err.error == sys::GDEXTENSION_CALL_OK {
            return Ok(());
        }

        let explicit_args_str = join_args(explicit_args.iter().map(|arg| arg.to_variant()));
        let vararg_str = if varargs.is_empty() {
            String::new()
        } else {
            format!(", [va] {}", join_args(varargs.iter().cloned()))
        };
        let call_expr = format!("{call_ctx}({explicit_args_str}{vararg_str})");

        // Check if the error originated from our own code (e.g. a #[func] returning Err or a panic).
        // If so, use the rich CallError stashed in the thread-local rather than decoding the opaque Godot error struct.
        if source_error.is_some() {
            let mut call_error = Self::new(call_ctx, String::new(), None);
            call_error.b.call_expr = call_expr;
            call_error.b.source = source_error.map(SourceError::Call);
            return Err(call_error);
        }

        let mut arg_types = Vec::with_capacity(explicit_args.len() + varargs.len());
        arg_types.extend(explicit_args.iter().map(|arg| arg.to_variant().get_type()));
        arg_types.extend(varargs.iter().map(Variant::get_type));

        Err(Self::failed_varcall_inner(
            call_ctx,
            call_expr,
            err,
            &arg_types,
            explicit_args.len(),
            None,
        ))
    }

    // ------------------------------------------------------------------------------------------------------------------------------------------
    // Constructors returning Self; guaranteed failure

    /// Returns an error for a failed parameter conversion.
    pub(crate) fn failed_param_conversion<P>(
        call_ctx: &CallContext,
        param_index: usize,
        convert_error: ConvertError,
    ) -> Self {
        let param_ty = sys::short_type_name::<P>();

        Self::new(
            call_ctx,
            format!("parameter #{param_index} ({param_ty}) conversion"),
            Some(convert_error),
        )
    }

    fn failed_param_conversion_engine(
        call_ctx: &CallContext,
        param_index: i32,
        actual: VariantType,
        expected: VariantType,
    ) -> Self {
        // Note: reason is same wording as in FromVariantError::description().
        let reason =
            format!("parameter #{param_index} -- cannot convert from {actual:?} to {expected:?}");

        Self::new(call_ctx, reason, None)
    }

    /// Returns an error for a failed return type conversion.
    ///
    /// **Note:** There are probably no practical scenarios where this occurs. Different calls:
    /// - outbound engine API: return values are statically typed (correct by binding) or Variant (infallible)
    /// - #[func] methods: dynamic calls return Variant
    /// - GDScript -> Rust calls: value is checked on GDScript side (at parse or runtime), not involving this.
    ///
    /// It might only occur if there are mistakes in the binding, or if we at some point add typed dynamic calls, à la `call<R>((1, "str"))`.
    pub(crate) fn failed_return_conversion<R>(
        call_ctx: &CallContext,
        convert_error: ConvertError,
    ) -> Self {
        let return_ty = std::any::type_name::<R>();

        Self::new(
            call_ctx,
            format!("return value {return_ty} conversion"),
            Some(convert_error),
        )
    }

    fn failed_param_count(
        call_ctx: &CallContext,
        arg_count: usize,
        param_count: usize,
    ) -> CallError {
        let param_plural = plural(param_count);
        let arg_plural = plural(arg_count);

        Self::new(
            call_ctx,
            format!(
                "function has {param_count} parameter{param_plural}, but received {arg_count} argument{arg_plural}"
            ),
            None,
        )
    }

    fn failed_varcall_inner(
        call_ctx: &CallContext,
        call_expr: String,
        err: sys::GDExtensionCallError,
        arg_types: &[VariantType],
        vararg_offset: usize,
        source: Option<SourceError>,
    ) -> Self {
        // This specializes on reflection-style calls, e.g. call(), rpc() etc.
        // In these cases, varargs are the _actual_ arguments, with required args being metadata such as method name.

        sys::strict_assert_ne!(err.error, sys::GDEXTENSION_CALL_OK); // already checked outside

        let sys::GDExtensionCallError {
            error,
            argument,
            expected,
        } = err;

        let mut call_error = match error {
            sys::GDEXTENSION_CALL_ERROR_INVALID_METHOD => {
                Self::new(call_ctx, "method not found", None)
            }
            sys::GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT => {
                // Index calculation relies on patterns like call("...", varargs), might not always work...
                let from = arg_types[vararg_offset + argument as usize];
                let to = VariantType::from_sys(expected as sys::GDExtensionVariantType);
                let i = argument + 1;

                Self::failed_param_conversion_engine(call_ctx, i, from, to)
            }
            sys::GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS
            | sys::GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS => {
                let arg_count = arg_types.len() - vararg_offset;
                let param_count = expected as usize;
                Self::failed_param_count(call_ctx, arg_count, param_count)
            }
            sys::GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL => {
                Self::new(call_ctx, "instance is null", None)
            }
            sys::GDEXTENSION_CALL_ERROR_METHOD_NOT_CONST => {
                Self::new(call_ctx, "method is not const", None)
            }
            _ => Self::new(
                call_ctx,
                format!("unknown reason (error code {error})"),
                None,
            ),
        };

        // Self {
        //     class_name: call_ctx.class_name.to_string(),
        //     function_name: call_ctx.function_name.to_string(),
        //     call_expr,
        //     reason,
        //     source,
        // }

        call_error.b.source = source;
        call_error.b.call_expr = call_expr;
        call_error
    }

    #[doc(hidden)]
    pub fn failed_by_user_panic(call_ctx: &CallContext, panic_payload: PanicPayload) -> Self {
        // Reason will not be printed, since panic itself already prints -- but is still accessible in CallError's Display/Error impls.
        let reason = format!("function panicked: {}", panic_payload.into_panic_message());

        let mut err = Self::new(call_ctx, reason, None);
        err.b.caused_by_panic = true;
        err
    }

    /// Whether this error was caused by a Rust panic (as opposed to a Godot or godot-rust error).
    ///
    /// If true, the panic hook has already printed the error; callers can avoid printing it again.
    pub(crate) fn caused_by_panic(&self) -> bool {
        self.b.caused_by_panic
    }

    fn new(
        call_ctx: &CallContext,
        reason: impl Into<String>,
        source: Option<ConvertError>,
    ) -> Self {
        let inner = InnerCallError {
            class_name: call_ctx.class_name.to_string(),
            function_name: call_ctx.function_name.to_string(),
            call_expr: format!("{call_ctx}()"),
            reason: reason.into(),
            source: source.map(|e| SourceError::Convert {
                value: e.value().map_or_else(String::new, |v| format!("{v:?}")),
                erased_error: e.into(),
            }),
            caused_by_panic: false, // set to true after construction if needed.
        };

        Self { b: Box::new(inner) }
    }

    /// Describes the error.
    ///
    /// This is the same as the `Display`/`ToString` repr, but without the prefix mentioning that this is a function call error,
    /// and without any source error information.
    pub fn message(&self, with_source: bool) -> String {
        let InnerCallError {
            call_expr,
            reason,
            source,
            ..
        } = &*self.b;

        let reason_str = if reason.is_empty() {
            String::new()
        } else {
            format!("\n    Reason: {reason}")
        };

        // let source_str = if with_source {
        //     self.source()
        //         .map(|e| format!("\n  Source: {}", e))
        //         .unwrap_or_default()
        // } else {
        //     String::new()
        // };

        let source_str = match source {
            Some(SourceError::Convert {
                erased_error,
                value,
            }) if with_source => {
                format!(
                    "\n  Source: {erased_error}{}{value}",
                    if value.is_empty() { "" } else { ": " },
                )
            }
            Some(SourceError::Call(e)) if with_source => {
                let message = e.message(true);
                format!("\n  Source: {message}")
            }
            _ => String::new(),
        };

        format!("{call_expr}{reason_str}{source_str}")
    }
}

impl fmt::Display for CallError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let message = self.message(true);
        write!(f, "godot-rust function call failed: {message}")
    }
}

impl fmt::Debug for CallError {
    // Delegate to inner box.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.b)
    }
}

impl Error for CallError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self.b.source.as_ref() {
            Some(SourceError::Convert {
                erased_error: e, ..
            }) => deref_to::<ErasedConvertError>(e),
            Some(SourceError::Call(e)) => deref_to::<CallError>(e),
            None => None,
        }
    }
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation

#[derive(Debug)]
enum SourceError {
    Convert {
        erased_error: ErasedConvertError,
        value: String,
    },

    // If the top-level Box on CallError is ever removed, this would need to store Box<CallError> again.
    Call(CallError),
}

/// Explicit dereferencing to a certain type. Avoids accidentally returning `&Box<T>` or so.
fn deref_to<T>(t: &T) -> Option<&(dyn Error + 'static)>
where
    T: Error + 'static,
{
    Some(t)
}

fn join_args(args: impl Iterator<Item = Variant>) -> String {
    join_debug(args)
}

fn plural(count: usize) -> &'static str {
    if count == 1 { "" } else { "s" }
}