From b06255a0e1af5e43594b7cc24f41aa1ce3ed6a05 Mon Sep 17 00:00:00 2001 From: devs6186 Date: Sun, 15 Mar 2026 20:02:08 +0530 Subject: [PATCH] fix(metacall): implement metacall_error_last/clear with thread-local store metacall_error_last() and metacall_error_clear() were stubs that returned 1 and did nothing. Any caller that checked the last error after a failed metacall received no information, making the published API unusable. Implement a thread-local value* (metacall_error_last_v) in metacall_error.c using the PORTABILITY_THREAD_LOCAL macro so the per-thread last-error slot works on GCC/Clang (__thread) and MSVC (__declspec(thread)) without changes to the calling code. metacall_error_last() now delegates to metacall_error_from_value() on the stored copy. metacall_error_clear() destroys the stored value and resets the slot. metacall_error_set_last() is the new internal entry point that saves a deep copy (via value_type_copy, which increments the exception reference count) so the caller retains ownership of the original. In metacallfv_s, after function_call returns, detect TYPE_THROWABLE via type_id_throwable() and call metacall_error_set_last(). This covers the common call path reached by metacallv/metacallv_s/metacallfv/metacallfv_s. The copy approach means the returned value and the internal slot are independent; destroying one does not invalidate the other. The existing metacall_python_exception_test already exercises metacall_error_from_value() directly. With this change, metacall_error_last() becomes available as an errno-style alternative for callers that do not inspect return values. Fixes #141 --- .../include/metacall/metacall_error.h | 12 +++++++ source/metacall/source/metacall.c | 5 +++ source/metacall/source/metacall_error.c | 35 ++++++++++++++++--- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/source/metacall/include/metacall/metacall_error.h b/source/metacall/include/metacall/metacall_error.h index 6324746795..07d25d228d 100644 --- a/source/metacall/include/metacall/metacall_error.h +++ b/source/metacall/include/metacall/metacall_error.h @@ -106,6 +106,18 @@ METACALL_API int metacall_error_last(metacall_exception ex); */ METACALL_API void metacall_error_clear(void); +/** +* @brief +* Record a throwable value as the last error for the calling thread. Called +* internally by metacallfv_s when a loader returns a TYPE_THROWABLE value. +* May also be called by ports or loaders that want to set the last error +* directly. Copies @v so the caller retains ownership of the original value. +* +* @param[in] v +* Throwable value to record; must be of type TYPE_THROWABLE +*/ +METACALL_API void metacall_error_set_last(void *v); + #ifdef __cplusplus } #endif diff --git a/source/metacall/source/metacall.c b/source/metacall/source/metacall.c index b35445b245..17cfdc21d2 100644 --- a/source/metacall/source/metacall.c +++ b/source/metacall/source/metacall.c @@ -1144,6 +1144,11 @@ void *metacallfv_s(void *func, void *args[], size_t size) ret = function_call(f, args, size); + if (ret != NULL && type_id_throwable(value_type_id(ret)) == 0) + { + metacall_error_set_last(ret); + } + if (ret != NULL) { type t = signature_get_return(s); diff --git a/source/metacall/source/metacall_error.c b/source/metacall/source/metacall_error.c index 503df87efa..bedb4df730 100644 --- a/source/metacall/source/metacall_error.c +++ b/source/metacall/source/metacall_error.c @@ -24,8 +24,14 @@ #include +#include + #include +/* -- Private Variables -- */ + +static PORTABILITY_THREAD_LOCAL value metacall_error_last_v = NULL; + /* -- Methods -- */ void *metacall_error_throw(const char *label, int64_t code, const char *stacktrace, const char *message, ...) @@ -92,13 +98,34 @@ int metacall_error_from_value(void *v, metacall_exception ex) int metacall_error_last(metacall_exception ex) { - // TODO - (void)ex; + if (metacall_error_last_v == NULL) + { + return 1; + } - return 1; + return metacall_error_from_value(metacall_error_last_v, ex); } void metacall_error_clear(void) { - // TODO + if (metacall_error_last_v != NULL) + { + value_type_destroy(metacall_error_last_v); + metacall_error_last_v = NULL; + } +} + +void metacall_error_set_last(void *v) +{ + if (v == NULL || type_id_throwable(value_type_id((value)v)) != 0) + { + return; + } + + if (metacall_error_last_v != NULL) + { + value_type_destroy(metacall_error_last_v); + } + + metacall_error_last_v = value_type_copy((value)v); }