[gjs/ewlsh/chain-static: 16/16] Correctly chain constructor prototypes to enable static inheritance




commit 16032db6cd22f7d429011c8d0fbaf275c87f9ab5
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Sep 12 00:26:06 2021 -0700

    Correctly chain constructor prototypes to enable static inheritance
    
    In ES2015+ classes it is expected for constructors to inherit the
    static methods of their parent class, our implementation of
    classes previously did not do this as it was uncommon in ES5 era
    classes.

 gi/object.cpp                  | 48 ++++++++++++++++++++++++++++++++++++------
 gi/object.h                    |  3 +++
 installed-tests/js/testGtk3.js |  4 ++++
 installed-tests/js/testGtk4.js |  4 ++++
 4 files changed, 52 insertions(+), 7 deletions(-)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index f21a3c14..602a2875 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -2375,11 +2375,35 @@ JSPropertySpec ObjectBase::proto_properties[] = {
 bool ObjectPrototype::get_parent_proto(JSContext* cx,
                                        JS::MutableHandleObject proto) const {
     GType parent_type = g_type_parent(gtype());
-    if (parent_type != G_TYPE_INVALID) {
-        proto.set(gjs_lookup_object_prototype(cx, parent_type));
-        if (!proto)
-            return false;
+    if (parent_type == G_TYPE_INVALID) {
+        proto.set(nullptr);
+        return true;
     }
+
+    JSObject* prototype = gjs_lookup_object_prototype(cx, parent_type);
+    if (!prototype)
+        return false;
+
+    proto.set(prototype);
+    return true;
+}
+
+bool ObjectPrototype::get_parent_constructor(
+    JSContext* cx, JS::MutableHandleObject constructor) const {
+    GType parent_type = g_type_parent(gtype());
+
+    if (parent_type == G_TYPE_INVALID) {
+        constructor.set(nullptr);
+        return true;
+    }
+
+    JS::RootedValue v_constructor(cx);
+    if (!gjs_lookup_object_constructor(cx, parent_type, &v_constructor))
+        return false;
+
+    g_assert(v_constructor.isObject() &&
+             "gjs_lookup_object_constructor() should always produce an object");
+    constructor.set(&v_constructor.toObject());
     return true;
 }
 
@@ -2400,17 +2424,27 @@ bool ObjectPrototype::define_class(
     JSContext* context, JS::HandleObject in_object, GIObjectInfo* info,
     GType gtype, GType* interface_gtypes, uint32_t n_interface_gtypes,
     JS::MutableHandleObject constructor, JS::MutableHandleObject prototype) {
-    if (!ObjectPrototype::create_class(context, in_object, info, gtype,
-                                       constructor, prototype))
+    ObjectPrototype* priv = ObjectPrototype::create_class(
+        context, in_object, info, gtype, constructor, prototype);
+    if (!priv)
         return false;
 
-    ObjectPrototype* priv = ObjectPrototype::for_js(context, prototype);
     if (interface_gtypes) {
         for (uint32_t n = 0; n < n_interface_gtypes; n++) {
             priv->m_interface_gtypes.push_back(interface_gtypes[n]);
         }
     }
 
+    JS::RootedObject parent_constructor(context);
+    if (!priv->get_parent_constructor(context, &parent_constructor))
+        return false;
+    // If this is a fundamental constructor (e.g. GObject.Object) the
+    // parent constructor may be null.
+    if (parent_constructor) {
+        if (!JS_SetPrototype(context, constructor, parent_constructor))
+            return false;
+    }
+
     // hook_up_vfunc and the signal handler matcher functions can't be included
     // in gjs_object_instance_proto_funcs because they are custom symbols.
     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
diff --git a/gi/object.h b/gi/object.h
index a02bc8ab..c3fbad2f 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -215,6 +215,9 @@ class ObjectPrototype
  private:
     GJS_JSAPI_RETURN_CONVENTION
     bool get_parent_proto(JSContext* cx, JS::MutableHandleObject proto) const;
+    GJS_JSAPI_RETURN_CONVENTION
+    bool get_parent_constructor(JSContext* cx,
+                                JS::MutableHandleObject constructor) const;
 
     [[nodiscard]] bool is_vfunc_unchanged(GIVFuncInfo* info);
     static void vfunc_invalidated_notify(void* data, GClosure* closure);
diff --git a/installed-tests/js/testGtk3.js b/installed-tests/js/testGtk3.js
index 27450fea..83ca8247 100644
--- a/installed-tests/js/testGtk3.js
+++ b/installed-tests/js/testGtk3.js
@@ -174,6 +174,10 @@ describe('Gtk overrides', function () {
         expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass');
     });
 
+    it('static inheritance works', function () {
+        expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass');
+    });
+
     it('avoid crashing when GTK vfuncs are called in garbage collection', function () {
         GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_CRITICAL,
             '*during garbage collection*');
diff --git a/installed-tests/js/testGtk4.js b/installed-tests/js/testGtk4.js
index 2d458610..d7fc0028 100644
--- a/installed-tests/js/testGtk4.js
+++ b/installed-tests/js/testGtk4.js
@@ -184,6 +184,10 @@ describe('Gtk overrides', function () {
         expect(Gtk.Widget.get_css_name.call(MyComplexGtkSubclass)).toEqual('complex-subclass');
     });
 
+    it('static inheritance works', function () {
+        expect(MyComplexGtkSubclass.get_css_name()).toEqual('complex-subclass');
+    });
+
     it('can create a Gtk.TreeIter with accessible stamp field', function () {
         const iter = new Gtk.TreeIter();
         iter.stamp = 42;


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