[gjs/gnome-40] arg-cache: Never throw when building the argument cache



commit e95879fcc41feb6f6f839dfe5b4affa95098a6e7
Author: Philip Chimento <philip chimento gmail com>
Date:   Sat Mar 20 19:11:26 2021 -0700

    arg-cache: Never throw when building the argument cache
    
    Previously, the methods for building the argument cache for a function
    could fail, and throw an exception. Normally this was not a problem, since
    the argument cache was built when creating the Function object, and that
    was generally created right before it was about to be called.
    
    However, static methods are created eagerly when defining a type's
    constructor, so if a type contained a method that would throw when
    building the argument cache, then merely resolving that type would throw.
    (For example, resolving `GLib.ByteArray` would throw.)
    
    Instead, change all the argument cache building methods to be infallible.
    If an argument is not supported in GJS, then its in-marshaller throws
    instead. This causes the error to happen when the Function object is
    called, not when it is created.
    
    In order to give good exception messages, we add a "reason" field to the
    GjsArgumentCache union, used by the new gjs_marshal_not_introspectable_in
    marshaller, and a display_name() method to GjsFunctionCallState.
    
    Closes: #386

 gi/arg-cache.cpp                        | 190 +++++++++++++++-----------------
 gi/arg-cache.h                          |  24 +++-
 gi/function.cpp                         |  16 +--
 gi/function.h                           |  13 +++
 installed-tests/js/testGIMarshalling.js |   4 +-
 installed-tests/js/testIntrospection.js |   2 +
 installed-tests/js/testRegress.js       |   8 ++
 7 files changed, 138 insertions(+), 119 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 97d15325..f68cb4f6 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -113,30 +113,6 @@ static void gjs_g_argument_set_array_length(GITypeTag tag, GIArgument* arg,
     }
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool throw_not_introspectable_argument(JSContext* cx,
-                                              GICallableInfo* function,
-                                              const char* arg_name) {
-    gjs_throw(cx,
-              "Function %s.%s cannot be called: argument '%s' is not "
-              "introspectable.",
-              g_base_info_get_namespace(function),
-              g_base_info_get_name(function), arg_name);
-    return false;
-}
-
-GJS_JSAPI_RETURN_CONVENTION
-static bool throw_not_introspectable_unboxed_type(JSContext* cx,
-                                                  GICallableInfo* function,
-                                                  const char* arg_name) {
-    gjs_throw(cx,
-              "Function %s.%s cannot be called: unexpected unregistered type "
-              "for argument '%s'.",
-              g_base_info_get_namespace(function),
-              g_base_info_get_name(function), arg_name);
-    return false;
-}
-
 GJS_JSAPI_RETURN_CONVENTION
 static bool report_typeof_mismatch(JSContext* cx, const char* arg_name,
                                    JS::HandleValue value,
@@ -181,6 +157,34 @@ static bool gjs_marshal_skipped_in(JSContext*, GjsArgumentCache*,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_not_introspectable_in(JSContext* cx,
+                                              GjsArgumentCache* self,
+                                              GjsFunctionCallState* state,
+                                              GIArgument*, JS::HandleValue) {
+    static const char* reason_strings[] = {
+        "callback out-argument",
+        "DestroyNotify argument with no callback",
+        "DestroyNotify argument with no user data",
+        "type not supported for (transfer container)",
+        "type not supported for (out caller-allocates)",
+        "boxed type with transfer not registered as a GType",
+        "union type not registered as a GType",
+        "type not supported by introspection",
+    };
+    static_assert(
+        G_N_ELEMENTS(reason_strings) == NotIntrospectableReason::LAST_REASON,
+        "Explanations must match the values in NotIntrospectableReason");
+
+    gjs_throw(cx,
+              "Function %s() cannot be called: argument '%s' with type %s is "
+              "not introspectable because it has a %s",
+              state->display_name().get(), self->arg_name,
+              g_type_tag_to_string(g_type_info_get_tag(&self->type_info)),
+              reason_strings[self->contents.reason]);
+    return false;
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool gjs_marshal_generic_in_in(JSContext* cx, GjsArgumentCache* self,
                                       GjsFunctionCallState*, GIArgument* arg,
@@ -1051,9 +1055,9 @@ static const GjsArgumentMarshallers fallback_out_marshallers = {
 };
 
 static const GjsArgumentMarshallers invalid_in_marshallers = {
-    nullptr,  // no in, will cause the function invocation code to throw
-    gjs_marshal_skipped_out,  // out
-    gjs_marshal_skipped_release,  // release
+    gjs_marshal_not_introspectable_in,  // in, always throws
+    gjs_marshal_skipped_out,            // out
+    gjs_marshal_skipped_release,        // release
 };
 
 static const GjsArgumentMarshallers enum_in_marshallers = {
@@ -1253,7 +1257,7 @@ static inline void gjs_arg_cache_set_skip_all(GjsArgumentCache* self) {
         static_cast<GjsArgumentFlags>(GjsArgumentFlags::SKIP_IN | GjsArgumentFlags::SKIP_OUT);
 }
 
-bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
+void gjs_arg_cache_build_return(GjsArgumentCache* self,
                                 GjsArgumentCache* arguments,
                                 GICallableInfo* callable,
                                 bool* inc_counter_out) {
@@ -1265,7 +1269,7 @@ bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
         !g_type_info_is_pointer(&self->type_info)) {
         *inc_counter_out = false;
         gjs_arg_cache_set_skip_all(self);
-        return true;
+        return;
     }
 
     *inc_counter_out = true;
@@ -1292,7 +1296,7 @@ bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
             g_arg_info_load_type(&length_arg, &length_type);
             self->contents.array.length_tag = g_type_info_get_tag(&length_type);
 
-            return true;
+            return;
         }
     }
 
@@ -1300,8 +1304,6 @@ bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
     // used in the failure release path)
     self->flags = static_cast<GjsArgumentFlags>(self->flags | GjsArgumentFlags::SKIP_IN);
     self->marshallers = &return_value_marshallers;
-
-    return true;
 }
 
 static void gjs_arg_cache_build_enum_bounds(GjsArgumentCache* self,
@@ -1356,28 +1358,29 @@ static void gjs_arg_cache_build_flags_mask(GjsArgumentCache* self,
            strcmp("Gdk", g_base_info_get_namespace(info)) == 0;
 }
 
-static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
-                                                 GjsArgumentCache* self,
-                                                 GICallableInfo* callable,
+static void gjs_arg_cache_build_interface_in_arg(GjsArgumentCache* self,
                                                  GIBaseInfo* interface_info,
                                                  bool is_instance_param) {
     GIInfoType interface_type = g_base_info_get_type(interface_info);
 
     // We do some transfer magic later, so let's ensure we don't mess up.
     // Should not happen in practice.
-    if (G_UNLIKELY(self->transfer == GI_TRANSFER_CONTAINER))
-        return throw_not_introspectable_argument(cx, callable, self->arg_name);
+    if (G_UNLIKELY(self->transfer == GI_TRANSFER_CONTAINER)) {
+        self->marshallers = &invalid_in_marshallers;
+        self->contents.reason = INTERFACE_TRANSFER_CONTAINER;
+        return;
+    }
 
     switch (interface_type) {
         case GI_INFO_TYPE_ENUM:
             gjs_arg_cache_build_enum_bounds(self, interface_info);
             self->marshallers = &enum_in_marshallers;
-            return true;
+            return;
 
         case GI_INFO_TYPE_FLAGS:
             gjs_arg_cache_build_flags_mask(self, interface_info);
             self->marshallers = &flags_in_marshallers;
-            return true;
+            return;
 
         case GI_INFO_TYPE_STRUCT:
             if (g_struct_info_is_foreign(interface_info)) {
@@ -1385,7 +1388,7 @@ static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
                     self->marshallers = &foreign_struct_instance_in_marshallers;
                 else
                     self->marshallers = &foreign_struct_in_marshallers;
-                return true;
+                return;
             }
             [[fallthrough]];
         case GI_INFO_TYPE_BOXED:
@@ -1406,13 +1409,13 @@ static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
                     self->marshallers = &gvalue_in_transfer_none_marshallers;
                 else
                     self->marshallers = &gvalue_in_marshallers;
-                return true;
+                return;
             }
 
             if (is_gdk_atom(interface_info)) {
                 // Fall back to the generic marshaller
                 self->marshallers = &fallback_interface_in_marshallers;
-                return true;
+                return;
             }
 
             if (gtype == G_TYPE_CLOSURE) {
@@ -1420,7 +1423,7 @@ static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
                     self->marshallers = &gclosure_in_transfer_none_marshallers;
                 else
                     self->marshallers = &gclosure_in_marshallers;
-                return true;
+                return;
             }
 
             if (gtype == G_TYPE_BYTES) {
@@ -1428,51 +1431,52 @@ static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
                     self->marshallers = &gbytes_in_transfer_none_marshallers;
                 else
                     self->marshallers = &gbytes_in_marshallers;
-                return true;
+                return;
             }
 
             if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
                 self->marshallers = &object_in_marshallers;
-                return true;
+                return;
             }
 
             if (g_type_is_a(gtype, G_TYPE_PARAM)) {
                 // Fall back to the generic marshaller
                 self->marshallers = &fallback_interface_in_marshallers;
-                return true;
+                return;
             }
 
             if (interface_type == GI_INFO_TYPE_UNION) {
                 if (gtype == G_TYPE_NONE) {
                     // Can't handle unions without a GType
-                    return throw_not_introspectable_unboxed_type(
-                        cx, callable, self->arg_name);
+                    self->marshallers = &invalid_in_marshallers;
+                    self->contents.reason = UNREGISTERED_UNION;
+                    return;
                 }
 
                 self->marshallers = &union_in_marshallers;
-                return true;
+                return;
             }
 
             if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
                 self->marshallers = &fundamental_in_marshallers;
-                return true;
+                return;
             }
 
             if (g_type_is_a(gtype, G_TYPE_INTERFACE)) {
                 self->marshallers = &interface_in_marshallers;
-                return true;
+                return;
             }
 
             // generic boxed type
             if (gtype == G_TYPE_NONE && self->transfer != GI_TRANSFER_NOTHING) {
                 // Can't transfer ownership of a structure type not
                 // registered as a boxed
-                return throw_not_introspectable_unboxed_type(cx, callable,
-                                                             self->arg_name);
+                self->marshallers = &invalid_in_marshallers;
+                self->contents.reason = UNREGISTERED_BOXED_WITH_TRANSFER;
             }
 
             self->marshallers = &boxed_in_marshallers;
-            return true;
+            return;
         } break;
 
         case GI_INFO_TYPE_INVALID:
@@ -1491,15 +1495,12 @@ static bool gjs_arg_cache_build_interface_in_arg(JSContext* cx,
         default:
             // Don't know how to handle this interface type (should not happen
             // in practice, for typelibs emitted by g-ir-compiler)
-            return throw_not_introspectable_argument(cx, callable,
-                                                     self->arg_name);
+            self->marshallers = &invalid_in_marshallers;
+            self->contents.reason = UNSUPPORTED_TYPE;
     }
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool gjs_arg_cache_build_normal_in_arg(JSContext* cx,
-                                              GjsArgumentCache* self,
-                                              GICallableInfo* callable,
+static void gjs_arg_cache_build_normal_in_arg(GjsArgumentCache* self,
                                               GITypeTag tag) {
     // "Normal" in arguments are those arguments that don't require special
     // processing, and don't touch other arguments.
@@ -1563,9 +1564,9 @@ static bool gjs_arg_cache_build_normal_in_arg(JSContext* cx,
         case GI_TYPE_TAG_INTERFACE: {
             GjsAutoBaseInfo interface_info =
                 g_type_info_get_interface(&self->type_info);
-            return gjs_arg_cache_build_interface_in_arg(
-                cx, self, callable, interface_info,
-                /* is_instance_param = */ false);
+            gjs_arg_cache_build_interface_in_arg(
+                self, interface_info, /* is_instance_param = */ false);
+            return;
         }
 
         case GI_TYPE_TAG_ARRAY:
@@ -1577,11 +1578,9 @@ static bool gjs_arg_cache_build_normal_in_arg(JSContext* cx,
             // FIXME: Falling back to the generic marshaller
             self->marshallers = &fallback_in_marshallers;
     }
-
-    return true;
 }
 
-bool gjs_arg_cache_build_instance(JSContext* cx, GjsArgumentCache* self,
+void gjs_arg_cache_build_instance(GjsArgumentCache* self,
                                   GICallableInfo* callable) {
     GIBaseInfo* interface_info = g_base_info_get_container(callable);  // !owned
 
@@ -1597,23 +1596,22 @@ bool gjs_arg_cache_build_instance(JSContext* cx, GjsArgumentCache* self,
     if (info_type == GI_INFO_TYPE_STRUCT &&
         g_struct_info_is_gtype_struct(interface_info)) {
         self->marshallers = &gtype_struct_instance_in_marshallers;
-        return true;
+        return;
     }
     if (info_type == GI_INFO_TYPE_OBJECT) {
         GType gtype = g_registered_type_info_get_g_type(interface_info);
 
         if (g_type_is_a(gtype, G_TYPE_PARAM)) {
             self->marshallers = &param_instance_in_marshallers;
-            return true;
+            return;
         }
     }
 
-    return gjs_arg_cache_build_interface_in_arg(cx, self, callable,
-                                                interface_info,
-                                                /* is_instance_param = */ true);
+    gjs_arg_cache_build_interface_in_arg(self, interface_info,
+                                         /* is_instance_param = */ true);
 }
 
-bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
+void gjs_arg_cache_build_arg(GjsArgumentCache* self,
                              GjsArgumentCache* arguments, uint8_t gi_index,
                              GIDirection direction, GIArgInfo* arg,
                              GICallableInfo* callable, bool* inc_counter_out) {
@@ -1642,11 +1640,9 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
     if (direction == GI_DIRECTION_OUT &&
         (flags & GjsArgumentFlags::CALLER_ALLOCATES)) {
         if (type_tag != GI_TYPE_TAG_INTERFACE) {
-            gjs_throw(cx,
-                      "Unsupported type %s for argument %s with (out "
-                      "caller-allocates)",
-                      g_type_tag_to_string(type_tag), self->arg_name);
-            return false;
+            self->marshallers = &invalid_in_marshallers;
+            self->contents.reason = OUT_CALLER_ALLOCATES_NON_STRUCT;
+            return;
         }
 
         GjsAutoBaseInfo interface_info =
@@ -1661,17 +1657,15 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
         } else if (interface_type == GI_INFO_TYPE_UNION) {
             size = g_union_info_get_size(interface_info);
         } else {
-            gjs_throw(cx,
-                      "Unsupported type %s for argument %s with (out "
-                      "caller-allocates)",
-                      g_info_type_to_string(interface_type), self->arg_name);
-            return false;
+            self->marshallers = &invalid_in_marshallers;
+            self->contents.reason = OUT_CALLER_ALLOCATES_NON_STRUCT;
+            return;
         }
 
         self->marshallers = &caller_allocates_out_marshallers;
         self->contents.caller_allocates_size = size;
 
-        return true;
+        return;
     }
 
     if (type_tag == GI_TYPE_TAG_INTERFACE) {
@@ -1680,12 +1674,9 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
         if (interface_info.type() == GI_INFO_TYPE_CALLBACK) {
             if (direction != GI_DIRECTION_IN) {
                 // Can't do callbacks for out or inout
-                gjs_throw(cx,
-                          "Function %s.%s has a callback out-argument %s, not "
-                          "supported",
-                          g_base_info_get_namespace(callable),
-                          g_base_info_get_name(callable), self->arg_name);
-                return false;
+                self->marshallers = &invalid_in_marshallers;
+                self->contents.reason = CALLBACK_OUT;
+                return;
             }
 
             if (strcmp(interface_info.name(), "DestroyNotify") == 0 &&
@@ -1697,6 +1688,7 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                 // then this is probably an unsupported function, so the
                 // function invocation code will check this and throw.
                 self->marshallers = &invalid_in_marshallers;
+                self->contents.reason = DESTROY_NOTIFY_NO_CALLBACK;
                 *inc_counter_out = false;
             } else {
                 self->marshallers = &callback_in_marshallers;
@@ -1711,12 +1703,9 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                     gjs_arg_cache_set_skip_all(&arguments[closure_pos]);
 
                 if (destroy_pos >= 0 && closure_pos < 0) {
-                    gjs_throw(cx,
-                              "Function %s.%s has a GDestroyNotify but no "
-                              "user_data, not supported",
-                              g_base_info_get_namespace(callable),
-                              g_base_info_get_name(callable));
-                    return false;
+                    self->marshallers = &invalid_in_marshallers;
+                    self->contents.reason = DESTROY_NOTIFY_NO_USER_DATA;
+                    return;
                 }
 
                 self->contents.callback.scope = g_arg_info_get_scope(arg);
@@ -1724,7 +1713,7 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                 self->set_callback_closure_pos(closure_pos);
             }
 
-            return true;
+            return;
         }
     }
 
@@ -1766,17 +1755,14 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                 *inc_counter_out = false;
             }
 
-            return true;
+            return;
         }
     }
 
     if (direction == GI_DIRECTION_IN)
-        return gjs_arg_cache_build_normal_in_arg(cx, self, callable, type_tag);
-
-    if (direction == GI_DIRECTION_INOUT)
+        gjs_arg_cache_build_normal_in_arg(self, type_tag);
+    else if (direction == GI_DIRECTION_INOUT)
         self->marshallers = &fallback_inout_marshallers;
     else
         self->marshallers = &fallback_out_marshallers;
-
-    return true;
 }
diff --git a/gi/arg-cache.h b/gi/arg-cache.h
index 97648577..b01c1add 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -24,6 +24,18 @@
 struct GjsFunctionCallState;
 struct GjsArgumentCache;
 
+enum NotIntrospectableReason : uint8_t {
+    CALLBACK_OUT,
+    DESTROY_NOTIFY_NO_CALLBACK,
+    DESTROY_NOTIFY_NO_USER_DATA,
+    INTERFACE_TRANSFER_CONTAINER,
+    OUT_CALLER_ALLOCATES_NON_STRUCT,
+    UNREGISTERED_BOXED_WITH_TRANSFER,
+    UNREGISTERED_UNION,
+    UNSUPPORTED_TYPE,
+    LAST_REASON
+};
+
 struct GjsArgumentMarshallers {
     bool (*in)(JSContext* cx, GjsArgumentCache* cache,
                GjsFunctionCallState* state, GIArgument* in_argument,
@@ -81,6 +93,9 @@ struct GjsArgumentCache {
 
         // out caller allocates (FIXME: should be in object)
         size_t caller_allocates_size;
+
+        // not introspectable
+        NotIntrospectableReason reason;
     } contents;
 
     GJS_JSAPI_RETURN_CONVENTION
@@ -158,20 +173,17 @@ static_assert(sizeof(GjsArgumentCache) <= 112,
               "function.");
 #endif  // x86-64 clang
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
+void gjs_arg_cache_build_arg(GjsArgumentCache* self,
                              GjsArgumentCache* arguments, uint8_t gi_index,
                              GIDirection direction, GIArgInfo* arg,
                              GICallableInfo* callable, bool* inc_counter_out);
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_return(JSContext* cx, GjsArgumentCache* self,
+void gjs_arg_cache_build_return(GjsArgumentCache* self,
                                 GjsArgumentCache* arguments,
                                 GICallableInfo* callable,
                                 bool* inc_counter_out);
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_instance(JSContext* cx, GjsArgumentCache* self,
+void gjs_arg_cache_build_instance(GjsArgumentCache* self,
                                   GICallableInfo* callable);
 
 [[nodiscard]] size_t gjs_g_argument_get_array_length(GITypeTag tag,
diff --git a/gi/function.cpp b/gi/function.cpp
index e378f72b..676f63b2 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -1258,14 +1258,12 @@ bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) {
     size_t offset = is_method ? 2 : 1;
     m_arguments = g_new0(GjsArgumentCache, n_args + offset) + offset;
 
-    if (is_method &&
-        !gjs_arg_cache_build_instance(context, &m_arguments[-2], m_info))
-        return false;
+    if (is_method)
+        gjs_arg_cache_build_instance(&m_arguments[-2], m_info);
 
     bool inc_counter;
-    if (!gjs_arg_cache_build_return(context, &m_arguments[-1], m_arguments,
-                                    m_info, &inc_counter))
-        return false;
+    gjs_arg_cache_build_return(&m_arguments[-1], m_arguments, m_info,
+                               &inc_counter);
 
     if (inc_counter)
         m_js_out_argc++;
@@ -1280,10 +1278,8 @@ bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) {
         g_callable_info_load_arg(m_info, i, &arg_info);
         direction = g_arg_info_get_direction(&arg_info);
 
-        if (!gjs_arg_cache_build_arg(context, &m_arguments[i], m_arguments, i,
-                                     direction, &arg_info, m_info,
-                                     &inc_counter))
-            return false;
+        gjs_arg_cache_build_arg(&m_arguments[i], m_arguments, i, direction,
+                                &arg_info, m_info, &inc_counter);
 
         if (inc_counter) {
             switch (direction) {
diff --git a/gi/function.h b/gi/function.h
index 759a01dd..318974ed 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -93,6 +93,7 @@ struct GjsFunctionCallState {
     JS::RootedObject instance_object;
     JS::RootedValueVector return_values;
     GjsAutoError local_error;
+    GICallableInfo* info;
     int gi_argc = 0;
     unsigned processed_c_args = 0;
     bool failed : 1;
@@ -102,6 +103,7 @@ struct GjsFunctionCallState {
     GjsFunctionCallState(JSContext* cx, GICallableInfo* callable)
         : instance_object(cx),
           return_values(cx),
+          info(callable),
           gi_argc(g_callable_info_get_n_args(callable)),
           failed(false),
           can_throw_gerror(g_callable_info_can_throw_gerror(callable)),
@@ -128,6 +130,17 @@ struct GjsFunctionCallState {
     }
 
     constexpr bool call_completed() { return !failed && !did_throw_gerror(); }
+
+    [[nodiscard]] GjsAutoChar display_name() {
+        GIBaseInfo* container = g_base_info_get_container(info);  // !owned
+        if (container) {
+            return g_strdup_printf(
+                "%s.%s.%s", g_base_info_get_namespace(container),
+                g_base_info_get_name(container), g_base_info_get_name(info));
+        }
+        return g_strdup_printf("%s.%s", g_base_info_get_namespace(info),
+                               g_base_info_get_name(info));
+    }
 };
 
 GJS_JSAPI_RETURN_CONVENTION
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index 63e4b99a..17336af8 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -546,8 +546,10 @@ describe('GArray', function () {
         // the test should be replaced with the one above when issue
         // https://gitlab.gnome.org/GNOME/gjs/issues/106 is fixed.
         it('marshals as a transfer-full caller-allocated out parameter throws errors', function () {
+            // should throw when called, not when the function object is created
+            expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated).not.toThrow();
             expect(() => GIMarshallingTests.garray_utf8_full_out_caller_allocated())
-                .toThrowError(/Unsupported type array.*\(out caller-allocates\)/);
+                .toThrowError(/type array.*\(out caller-allocates\)/);
         });
     });
 
diff --git a/installed-tests/js/testIntrospection.js b/installed-tests/js/testIntrospection.js
index 75f1a961..5e2ee7df 100644
--- a/installed-tests/js/testIntrospection.js
+++ b/installed-tests/js/testIntrospection.js
@@ -12,6 +12,8 @@ const System = imports.system;
 
 describe('GLib.DestroyNotify parameter', function () {
     it('throws when encountering a GDestroyNotify not associated with a callback', function () {
+        // should throw when called, not when the function object is created
+        expect(() => Gio.MemoryInputStream.new_from_data).not.toThrow();
         // the 'destroy' argument applies to the data, which is not supported in
         // gobject-introspection
         expect(() => Gio.MemoryInputStream.new_from_data('foobar'))
diff --git a/installed-tests/js/testRegress.js b/installed-tests/js/testRegress.js
index f067f1a1..5618b429 100644
--- a/installed-tests/js/testRegress.js
+++ b/installed-tests/js/testRegress.js
@@ -1383,6 +1383,14 @@ describe('Life, the Universe and Everything', function () {
         expect(callback2).toHaveBeenCalledTimes(2);
     }).pend('Callback with destroy-notify and no user data not currently supported');
 
+    // If this is ever supported, then replace it with the above test.
+    it('callback with destroy-notify and no user data throws error', function () {
+        // should throw when called, not when the function object is created
+        expect(() => Regress.test_callback_destroy_notify_no_user_data).not.toThrow();
+        expect(() => Regress.test_callback_destroy_notify_no_user_data(() => {}))
+            .toThrowError(/no user data/);
+    });
+
     it('async callback', function () {
         Regress.test_callback_async(() => 44);
         expect(Regress.test_callback_thaw_async()).toEqual(44);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]