[gjs/ewlsh/enumerable-interfaces] gi: Add enumeration hook for Interface prototypes




commit 6a5cc27d7c559a2b0886d564855a701582ec1e90
Author: Evan Welsh <contact evanwelsh com>
Date:   Sat Jan 8 22:40:35 2022 -0800

    gi: Add enumeration hook for Interface prototypes

 gi/interface.cpp                  |  75 ++++++++++++++++++++++++++-
 gi/interface.h                    |   5 ++
 modules/core/overrides/GObject.js |   4 +-
 modules/script/_legacy.js         | 104 +++++++++++++++++++-------------------
 4 files changed, 133 insertions(+), 55 deletions(-)
---
diff --git a/gi/interface.cpp b/gi/interface.cpp
index 925097e8..1bcc7a50 100644
--- a/gi/interface.cpp
+++ b/gi/interface.cpp
@@ -8,8 +8,12 @@
 #include <girepository.h>
 
 #include <js/Class.h>
+#include <js/Id.h>  // for JSID_VOID, PropertyKey, jsid
 #include <js/TypeDecls.h>
 #include <js/Utility.h>  // for UniqueChars
+#include <jsapi.h>       // for JS_ReportOutOfMemory
+
+#include <utility>  // for forward
 
 #include "gi/function.h"
 #include "gi/interface.h"
@@ -31,6 +35,75 @@ InterfacePrototype::~InterfacePrototype(void) {
     GJS_DEC_COUNTER(interface);
 }
 
+bool InterfacePrototype::new_enumerate_impl(
+    JSContext* cx, JS::HandleObject obj [[maybe_unused]],
+    JS::MutableHandleIdVector properties,
+    bool only_enumerable [[maybe_unused]]) {
+    unsigned n_interfaces;
+    GType* interfaces = g_type_interfaces(gtype(), &n_interfaces);
+
+    for (unsigned k = 0; k < n_interfaces; k++) {
+        GjsAutoInterfaceInfo iface_info =
+            g_irepository_find_by_gtype(nullptr, interfaces[k]);
+
+        if (!iface_info) {
+            continue;
+        }
+
+        int n_methods = g_interface_info_get_n_methods(iface_info);
+        int n_properties = g_interface_info_get_n_properties(iface_info);
+        if (!properties.reserve(properties.length() + n_methods +
+                                n_properties)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        // Methods
+        for (int i = 0; i < n_methods; i++) {
+            GjsAutoFunctionInfo meth_info =
+                g_interface_info_get_method(iface_info, i);
+            GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+            if (flags & GI_FUNCTION_IS_METHOD) {
+                const char* name = meth_info.name();
+                jsid id = gjs_intern_string_to_id(cx, name);
+                if (id == JSID_VOID)
+                    return false;
+                properties.infallibleAppend(id);
+            }
+        }
+    }
+
+    g_free(interfaces);
+
+    if (info()) {
+        int n_methods = g_interface_info_get_n_methods(info());
+        int n_properties = g_interface_info_get_n_properties(info());
+        if (!properties.reserve(properties.length() + n_methods +
+                                n_properties)) {
+            JS_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        // Methods
+        for (int i = 0; i < n_methods; i++) {
+            GjsAutoFunctionInfo meth_info =
+                g_interface_info_get_method(info(), i);
+            GIFunctionInfoFlags flags = g_function_info_get_flags(meth_info);
+
+            if (flags & GI_FUNCTION_IS_METHOD) {
+                const char* name = meth_info.name();
+                jsid id = gjs_intern_string_to_id(cx, name);
+                if (id == JSID_VOID)
+                    return false;
+                properties.infallibleAppend(id);
+            }
+        }
+    }
+
+    return true;
+}
+
 // See GIWrapperBase::resolve().
 bool InterfacePrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
                                       JS::HandleId id, bool* resolved) {
@@ -111,7 +184,7 @@ const struct JSClassOps InterfaceBase::class_ops = {
     nullptr,  // addProperty
     nullptr,  // deleteProperty
     nullptr,  // enumerate
-    nullptr,  // newEnumerate
+    &InterfaceBase::new_enumerate,
     &InterfaceBase::resolve,
     nullptr,  // mayResolve
     &InterfaceBase::finalize,
diff --git a/gi/interface.h b/gi/interface.h
index 490b7093..9630b478 100644
--- a/gi/interface.h
+++ b/gi/interface.h
@@ -92,6 +92,11 @@ class InterfacePrototype
     bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                       bool* resolved);
 
+    GJS_JSAPI_RETURN_CONVENTION
+    bool new_enumerate_impl(JSContext* cx, JS::HandleObject obj,
+                            JS::MutableHandleIdVector properties,
+                            bool only_enumerable);
+
     // JS methods
 
     GJS_JSAPI_RETURN_CONVENTION
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 6bfaf144..7357b059 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -172,7 +172,9 @@ function _copyAllDescriptors(target, source, filter) {
     .concat(Object.getOwnPropertySymbols(source))
     .forEach(key => {
         let descriptor = Object.getOwnPropertyDescriptor(source, key);
-        Object.defineProperty(target, key, descriptor);
+
+        if (descriptor)
+            Object.defineProperty(target, key, descriptor);
     });
 }
 
diff --git a/modules/script/_legacy.js b/modules/script/_legacy.js
index 135d2517..3353ef27 100644
--- a/modules/script/_legacy.js
+++ b/modules/script/_legacy.js
@@ -192,27 +192,29 @@ Class.prototype._copyPropertyDescriptor = function (params, propertyObj, key) {
 Class.prototype._init = function (params) {
     let className = params.Name;
 
-    let propertyObj = { };
+    let propertyObj = {};
 
     let interfaces = params.Implements || [];
     interfaces.forEach(iface => {
         Object.getOwnPropertyNames(iface.prototype)
-        .filter(name => !name.startsWith('__') && name !== 'constructor')
-        .filter(name => !(name in this.prototype))
-        .forEach(name => {
-            let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
-                name);
-            // writable and enumerable are inherited, see note above
-            descriptor.configurable = false;
-            propertyObj[name] = descriptor;
-        });
+            .filter(name => !name.startsWith('__') && name !== 'constructor')
+            .filter(name => !(name in this.prototype))
+            .forEach(name => {
+                let descriptor = Object.getOwnPropertyDescriptor(iface.prototype,
+                    name);
+                if (descriptor) {
+                    // writable and enumerable are inherited, see note above
+                    descriptor.configurable = false;
+                    propertyObj[name] = descriptor;
+                }
+            });
     });
 
     Object.getOwnPropertyNames(params)
-    .filter(name =>
-        ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
-    .concat(Object.getOwnPropertySymbols(params))
-    .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
+        .filter(name =>
+            ['Name', 'Extends', 'Abstract', 'Implements'].indexOf(name) === -1)
+        .concat(Object.getOwnPropertySymbols(params))
+        .forEach(this._copyPropertyDescriptor.bind(this, params, propertyObj));
 
     Object.defineProperties(this.prototype, propertyObj);
     Object.defineProperties(this.prototype, {
@@ -251,18 +253,18 @@ function _getMetaInterface(params) {
         }
         return null;
     })
-    .reduce((best, candidate) => {
-        // This function reduces to the "most derived" meta interface in the list.
-        if (best === null)
-            return candidate;
-        if (candidate === null)
-            return best;
-        for (let sup = candidate; sup; sup = sup.__super__) {
-            if (sup === best)
+        .reduce((best, candidate) => {
+            // This function reduces to the "most derived" meta interface in the list.
+            if (best === null)
                 return candidate;
-        }
-        return best;
-    }, null);
+            if (candidate === null)
+                return best;
+            for (let sup = candidate; sup; sup = sup.__super__) {
+                if (sup === best)
+                    return candidate;
+            }
+            return best;
+        }, null);
 
     // If we reach this point and we don't know the meta-interface, then it's
     // most likely because there were only pure-C interfaces listed in Requires
@@ -347,20 +349,16 @@ Interface.prototype._check = function (proto) {
         // but is not preferred because it will be the C name. The last option
         // is just so that we print something if there is garbage in Requires.
         required.prototype.__name__ || required.name || required);
-    if (unfulfilledReqs.length > 0) {
-        throw new Error(`The following interfaces must be implemented before ${
-            this.prototype.__name__}: ${unfulfilledReqs.join(', ')}`);
-    }
+    if (unfulfilledReqs.length > 0)
+        throw new Error(`The following interfaces must be implemented before ${this.prototype.__name__}: 
${unfulfilledReqs.join(', ')}`);
+
 
     // Check that this interface's required methods are implemented
     let unimplementedFns = Object.getOwnPropertyNames(this.prototype)
-    .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
-    .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
-    if (unimplementedFns.length > 0) {
-        throw new Error(`The following members of ${
-            this.prototype.__name__} are not implemented yet: ${
-            unimplementedFns.join(', ')}`);
-    }
+        .filter(p => this.prototype[p] === Interface.UNIMPLEMENTED)
+        .filter(p => !(p in proto) || proto[p] === Interface.UNIMPLEMENTED);
+    if (unimplementedFns.length > 0)
+        throw new Error(`The following members of ${this.prototype.__name__} are not implemented yet: 
${unimplementedFns.join(', ')}`);
 };
 
 Interface.prototype.toString = function () {
@@ -372,25 +370,25 @@ Interface.prototype._init = function (params) {
 
     let propertyObj = {};
     Object.getOwnPropertyNames(params)
-    .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
-    .forEach(name => {
-        let descriptor = Object.getOwnPropertyDescriptor(params, name);
-
-        // Create wrappers on the interface object so that generics work (e.g.
-        // SomeInterface.some_function(this, blah) instead of
-        // SomeInterface.prototype.some_function.call(this, blah)
-        if (typeof descriptor.value === 'function') {
-            let interfaceProto = this.prototype;  // capture in closure
-            this[name] = function (thisObj, ...args) {
-                return interfaceProto[name].call(thisObj, ...args);
-            };
-        }
+        .filter(name => ['Name', 'Requires'].indexOf(name) === -1)
+        .forEach(name => {
+            let descriptor = Object.getOwnPropertyDescriptor(params, name);
+
+            // Create wrappers on the interface object so that generics work (e.g.
+            // SomeInterface.some_function(this, blah) instead of
+            // SomeInterface.prototype.some_function.call(this, blah)
+            if (typeof descriptor.value === 'function') {
+                let interfaceProto = this.prototype;  // capture in closure
+                this[name] = function (thisObj, ...args) {
+                    return interfaceProto[name].call(thisObj, ...args);
+                };
+            }
 
-        // writable and enumerable are inherited, see note in Class._init()
-        descriptor.configurable = false;
+            // writable and enumerable are inherited, see note in Class._init()
+            descriptor.configurable = false;
 
-        propertyObj[name] = descriptor;
-    });
+            propertyObj[name] = descriptor;
+        });
 
     Object.defineProperties(this.prototype, propertyObj);
     Object.defineProperties(this.prototype, {


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