From 70fac8528f7845dfa1bf51ec3679093db5731a5a Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 27 Nov 2019 11:53:46 -0800 Subject: [PATCH] object: add templated property descriptors Add static methods to `PropertyDescriptor` that allows the definition of accessors where the getter/setter is specified as a template parameter rather than a function parameter. This allows us to avoid heap-allocating callback data. --- doc/property_descriptor.md | 73 +++++++++++++++++++++---- napi-inl.h | 107 +++++++++++++++++++++++++++++++++++++ napi.h | 37 +++++++++++++ test/object/object.cc | 47 ++++++++++++++++ test/object/object.js | 26 +++++++++ 5 files changed, 281 insertions(+), 9 deletions(-) diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 324b62f74..09c335609 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -26,15 +26,9 @@ Void Init(Env env) { Object obj = Object::New(env); // Accessor - PropertyDescriptor pd1 = PropertyDescriptor::Accessor(env, - obj, - "pd1", - TestGetter); - PropertyDescriptor pd2 = PropertyDescriptor::Accessor(env, - obj, - "pd2", - TestGetter, - TestSetter); + PropertyDescriptor pd1 = PropertyDescriptor::Accessor("pd1"); + PropertyDescriptor pd2 = + PropertyDescriptor::Accessor("pd2"); // Function PropertyDescriptor pd3 = PropertyDescriptor::Function(env, "function", @@ -51,6 +45,26 @@ Void Init(Env env) { } ``` +## Types + +### PropertyDescriptor::GetterCallback + +```cpp +typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); +``` + +This is the signature of a getter function to be passed as a template parameter +to `PropertyDescriptor::Accessor`. + +### PropertyDescriptor::SetterCallback + +```cpp +typedef void (*GetterCallback)(const Napi::CallbackInfo& info); +``` + +This is the signature of a setter function to be passed as a template parameter +to `PropertyDescriptor::Accessor`. + ## Methods ### Constructor @@ -63,6 +77,47 @@ Napi::PropertyDescriptor::PropertyDescriptor (napi_property_descriptor desc); ### Accessor +```cpp +template +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +* `[template] Getter`: A getter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a read-only property. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Napi::Name` + +```cpp +template < +Napi::PropertyDescriptor::GetterCallback Getter, +Napi::PropertyDescriptor::SetterCallback Setter> +static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +* `[template] Getter`: A getter function. +* `[template] Setter`: A setter function. +* `[in] attributes`: Potential attributes for the getter function. +* `[in] data`: A pointer to data of any type, default is a null pointer. + +Returns a PropertyDescriptor that contains a read-write property. + +The name of the property can be any of the following types: +- `const char*` +- `const std::string &` +- `napi_value value` +- `Napi::Name` + ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor (___ name, Getter getter, diff --git a/napi-inl.h b/napi-inl.h index f72e1daea..241b6219f 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2732,6 +2732,113 @@ inline void CallbackInfo::SetData(void* data) { // PropertyDescriptor class //////////////////////////////////////////////////////////////////////////////// +template +PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + GetterCallbackWrapper, + nullptr, + nullptr, + attributes, + data + }); +} + +template +PropertyDescriptor +PropertyDescriptor::Accessor(const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor +PropertyDescriptor::Accessor(Name name, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({ + nullptr, + name, + nullptr, + GetterCallbackWrapper, + nullptr, + nullptr, + attributes, + data + }); +} + +template < +PropertyDescriptor::GetterCallback Getter, +PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(const char* utf8name, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({ + utf8name, + nullptr, + nullptr, + GetterCallbackWrapper, + SetterCallbackWrapper, + nullptr, + attributes, + data + }); +} + +template < +PropertyDescriptor::GetterCallback Getter, +PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template < +PropertyDescriptor::GetterCallback Getter, +PropertyDescriptor::SetterCallback Setter> +PropertyDescriptor +PropertyDescriptor::Accessor(Name name, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({ + nullptr, + name, + nullptr, + GetterCallbackWrapper, + SetterCallbackWrapper, + nullptr, + attributes, + data + }); +} + +template +napi_value +PropertyDescriptor::GetterCallbackWrapper(napi_env env, + napi_callback_info info) { + CallbackInfo cbInfo(env, info); + return Getter(cbInfo); +} + +template +napi_value +PropertyDescriptor::SetterCallbackWrapper(napi_env env, + napi_callback_info info) { + CallbackInfo cbInfo(env, info); + Setter(cbInfo); + return nullptr; +} + template inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env, diff --git a/napi.h b/napi.h index 2fc9b10f3..e0c2582be 100644 --- a/napi.h +++ b/napi.h @@ -1407,6 +1407,9 @@ namespace Napi { class PropertyDescriptor { public: + typedef Napi::Value (*GetterCallback)(const Napi::CallbackInfo& info); + typedef void (*SetterCallback)(const Napi::CallbackInfo& info); + #ifndef NODE_ADDON_API_DISABLE_DEPRECATED template static PropertyDescriptor Accessor(const char* utf8name, @@ -1474,6 +1477,36 @@ namespace Napi { void* data = nullptr); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor(Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template static PropertyDescriptor Accessor(Napi::Env env, Napi::Object object, @@ -1559,6 +1592,10 @@ namespace Napi { operator const napi_property_descriptor&() const; private: + template + static napi_value GetterCallbackWrapper(napi_env env, napi_callback_info info); + template + static napi_value SetterCallbackWrapper(napi_env env, napi_callback_info info); napi_property_descriptor _desc; }; diff --git a/test/object/object.cc b/test/object/object.cc index 32f2c237a..1c9ce59f9 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -85,6 +85,20 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Accessor(env, obj, "readwriteAccessor", TestGetter, TestSetter), PropertyDescriptor::Accessor(env, obj, "readonlyAccessorWithUserData", TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, "readwriteAccessorWithUserData", TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor("readonlyAccessorT"), + PropertyDescriptor::Accessor( + "readwriteAccessorT"), + PropertyDescriptor::Accessor( + "readonlyAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, + TestSetterWithUserData>("readwriteAccessorWithUserDataT", + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value("readonlyValue", trueValue), PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable), PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable), @@ -100,6 +114,12 @@ void DefineProperties(const CallbackInfo& info) { std::string str2("readwriteAccessor"); std::string str1a("readonlyAccessorWithUserData"); std::string str2a("readwriteAccessorWithUserData"); + + std::string str1t("readonlyAccessorT"); + std::string str2t("readwriteAccessorT"); + std::string str1at("readonlyAccessorWithUserDataT"); + std::string str2at("readwriteAccessorWithUserDataT"); + std::string str3("readonlyValue"); std::string str4("readwriteValue"); std::string str5("enumerableValue"); @@ -111,6 +131,18 @@ void DefineProperties(const CallbackInfo& info) { PropertyDescriptor::Accessor(env, obj, str2, TestGetter, TestSetter), PropertyDescriptor::Accessor(env, obj, str1a, TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, str2a, TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor(str1t), + PropertyDescriptor::Accessor(str2t), + PropertyDescriptor::Accessor(str1at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, + TestSetterWithUserData>(str2at, + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value(str3, trueValue), PropertyDescriptor::Value(str4, trueValue, napi_writable), PropertyDescriptor::Value(str5, trueValue, napi_enumerable), @@ -127,6 +159,21 @@ void DefineProperties(const CallbackInfo& info) { Napi::String::New(env, "readonlyAccessorWithUserData"), TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), PropertyDescriptor::Accessor(env, obj, Napi::String::New(env, "readwriteAccessorWithUserData"), TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast(holder)), + + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readwriteAccessorT")), + PropertyDescriptor::Accessor( + Napi::String::New(env, "readonlyAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Accessor< + TestGetterWithUserData, TestSetterWithUserData>( + Napi::String::New(env, "readwriteAccessorWithUserDataT"), + napi_property_attributes::napi_default, + reinterpret_cast(holder)), + PropertyDescriptor::Value( Napi::String::New(env, "readonlyValue"), trueValue), PropertyDescriptor::Value( diff --git a/test/object/object.js b/test/object/object.js index b4fe57dff..2660e4b6e 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -22,6 +22,7 @@ function test(binding) { const obj = {}; binding.object.defineProperties(obj, nameType); + // accessors assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); assert.strictEqual(obj.readonlyAccessor, true); @@ -44,6 +45,30 @@ function test(binding) { obj.readwriteAccessorWithUserData = -14; assert.strictEqual(obj.readwriteAccessorWithUserData, -14); + // templated accessors + assertPropertyIsNot(obj, 'readonlyAccessorT', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessorT', 'configurable'); + assert.strictEqual(obj.readonlyAccessorT, true); + + assertPropertyIsNot(obj, 'readonlyAccessorWithUserDataT', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessorWithUserDataT', 'configurable'); + assert.strictEqual(obj.readonlyAccessorWithUserDataT, -14, nameType); + + assertPropertyIsNot(obj, 'readwriteAccessorT', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessorT', 'configurable'); + obj.readwriteAccessorT = false; + assert.strictEqual(obj.readwriteAccessorT, false); + obj.readwriteAccessorT = true; + assert.strictEqual(obj.readwriteAccessorT, true); + + assertPropertyIsNot(obj, 'readwriteAccessorWithUserDataT', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessorWithUserDataT', 'configurable'); + obj.readwriteAccessorWithUserDataT = 2; + assert.strictEqual(obj.readwriteAccessorWithUserDataT, 2); + obj.readwriteAccessorWithUserDataT = -14; + assert.strictEqual(obj.readwriteAccessorWithUserDataT, -14); + + // values assertPropertyIsNot(obj, 'readonlyValue', 'writable'); assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); @@ -65,6 +90,7 @@ function test(binding) { assertPropertyIsNot(obj, 'configurableValue', 'enumerable'); assertPropertyIs(obj, 'configurableValue', 'configurable'); + // functions assertPropertyIsNot(obj, 'function', 'writable'); assertPropertyIsNot(obj, 'function', 'enumerable'); assertPropertyIsNot(obj, 'function', 'configurable');