Skip to content

Commit

Permalink
object: add templated property descriptors
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Gabriel Schulhof committed Dec 4, 2019
1 parent 734725e commit 189176b
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 9 deletions.
73 changes: 64 additions & 9 deletions doc/property_descriptor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestGetter>("pd1");
PropertyDescriptor pd2 =
PropertyDescriptor::Accessor<TestGetter, TestSetter>("pd2");
// Function
PropertyDescriptor pd3 = PropertyDescriptor::Function(env,
"function",
Expand All @@ -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 (*SetterCallback)(const Napi::CallbackInfo& info);
```
This is the signature of a setter function to be passed as a template parameter
to `PropertyDescriptor::Accessor`.
## Methods
### Constructor
Expand All @@ -63,6 +77,47 @@ Napi::PropertyDescriptor::PropertyDescriptor (napi_property_descriptor desc);

### Accessor

```cpp
template <Napi::PropertyDescriptor::GetterCallback Getter>
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,
Expand Down
102 changes: 102 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2732,6 +2732,108 @@ inline void CallbackInfo::SetData(void* data) {
// PropertyDescriptor class
////////////////////////////////////////////////////////////////////////////////

template <typename PropertyDescriptor::GetterCallback Getter>
PropertyDescriptor
PropertyDescriptor::Accessor(const char* utf8name,
napi_property_attributes attributes,
void* data) {
napi_property_descriptor desc = napi_property_descriptor();

desc.utf8name = utf8name;
desc.getter = &GetterCallbackWrapper<Getter>;
desc.attributes = attributes;
desc.data = data;

return desc;
}

template <typename PropertyDescriptor::GetterCallback Getter>
PropertyDescriptor
PropertyDescriptor::Accessor(const std::string& utf8name,
napi_property_attributes attributes,
void* data) {
return Accessor<Getter>(utf8name.c_str(), attributes, data);
}

template <typename PropertyDescriptor::GetterCallback Getter>
PropertyDescriptor
PropertyDescriptor::Accessor(Name name,
napi_property_attributes attributes,
void* data) {
napi_property_descriptor desc = napi_property_descriptor();

desc.name = name;
desc.getter = &GetterCallbackWrapper<Getter>;
desc.attributes = attributes;
desc.data = data;

return desc;
}

template <
typename PropertyDescriptor::GetterCallback Getter,
typename PropertyDescriptor::SetterCallback Setter>
PropertyDescriptor
PropertyDescriptor::Accessor(const char* utf8name,
napi_property_attributes attributes,
void* data) {

napi_property_descriptor desc = napi_property_descriptor();

desc.utf8name = utf8name;
desc.getter = &GetterCallbackWrapper<Getter>;
desc.setter = &SetterCallbackWrapper<Setter>;
desc.attributes = attributes;
desc.data = data;

return desc;
}

template <
typename PropertyDescriptor::GetterCallback Getter,
typename PropertyDescriptor::SetterCallback Setter>
PropertyDescriptor
PropertyDescriptor::Accessor(const std::string& utf8name,
napi_property_attributes attributes,
void* data) {
return Accessor<Getter, Setter>(utf8name.c_str(), attributes, data);
}

template <
typename PropertyDescriptor::GetterCallback Getter,
typename PropertyDescriptor::SetterCallback Setter>
PropertyDescriptor
PropertyDescriptor::Accessor(Name name,
napi_property_attributes attributes,
void* data) {
napi_property_descriptor desc = napi_property_descriptor();

desc.name = name;
desc.getter = &GetterCallbackWrapper<Getter>;
desc.setter = &SetterCallbackWrapper<Setter>;
desc.attributes = attributes;
desc.data = data;

return desc;
}

template <typename PropertyDescriptor::GetterCallback Getter>
napi_value
PropertyDescriptor::GetterCallbackWrapper(napi_env env,
napi_callback_info info) {
CallbackInfo cbInfo(env, info);
return Getter(cbInfo);
}

template <typename PropertyDescriptor::SetterCallback Setter>
napi_value
PropertyDescriptor::SetterCallbackWrapper(napi_env env,
napi_callback_info info) {
CallbackInfo cbInfo(env, info);
Setter(cbInfo);
return nullptr;
}

template <typename Getter>
inline PropertyDescriptor
PropertyDescriptor::Accessor(Napi::Env env,
Expand Down
37 changes: 37 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename Getter>
static PropertyDescriptor Accessor(const char* utf8name,
Expand Down Expand Up @@ -1474,6 +1477,36 @@ namespace Napi {
void* data = nullptr);
#endif // !NODE_ADDON_API_DISABLE_DEPRECATED

template <GetterCallback Getter>
static PropertyDescriptor Accessor(const char* utf8name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <GetterCallback Getter>
static PropertyDescriptor Accessor(const std::string& utf8name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <GetterCallback Getter>
static PropertyDescriptor Accessor(Name name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <GetterCallback Getter, SetterCallback Setter>
static PropertyDescriptor Accessor(const char* utf8name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <GetterCallback Getter, SetterCallback Setter>
static PropertyDescriptor Accessor(const std::string& utf8name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <GetterCallback Getter, SetterCallback Setter>
static PropertyDescriptor Accessor(Name name,
napi_property_attributes attributes = napi_default,
void* data = nullptr);

template <typename Getter>
static PropertyDescriptor Accessor(Napi::Env env,
Napi::Object object,
Expand Down Expand Up @@ -1559,6 +1592,10 @@ namespace Napi {
operator const napi_property_descriptor&() const;

private:
template <GetterCallback Getter>
static napi_value GetterCallbackWrapper(napi_env env, napi_callback_info info);
template <SetterCallback Setter>
static napi_value SetterCallbackWrapper(napi_env env, napi_callback_info info);
napi_property_descriptor _desc;
};

Expand Down
47 changes: 47 additions & 0 deletions test/object/object.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<void*>(holder)),
PropertyDescriptor::Accessor(env, obj, "readwriteAccessorWithUserData", TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast<void*>(holder)),

PropertyDescriptor::Accessor<TestGetter>("readonlyAccessorT"),
PropertyDescriptor::Accessor<TestGetter, TestSetter>(
"readwriteAccessorT"),
PropertyDescriptor::Accessor<TestGetterWithUserData>(
"readonlyAccessorWithUserDataT",
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),
PropertyDescriptor::Accessor<
TestGetterWithUserData,
TestSetterWithUserData>("readwriteAccessorWithUserDataT",
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),

PropertyDescriptor::Value("readonlyValue", trueValue),
PropertyDescriptor::Value("readwriteValue", trueValue, napi_writable),
PropertyDescriptor::Value("enumerableValue", trueValue, napi_enumerable),
Expand All @@ -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");
Expand All @@ -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<void*>(holder)),
PropertyDescriptor::Accessor(env, obj, str2a, TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast<void*>(holder)),

PropertyDescriptor::Accessor<TestGetter>(str1t),
PropertyDescriptor::Accessor<TestGetter, TestSetter>(str2t),
PropertyDescriptor::Accessor<TestGetterWithUserData>(str1at,
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),
PropertyDescriptor::Accessor<
TestGetterWithUserData,
TestSetterWithUserData>(str2at,
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),

PropertyDescriptor::Value(str3, trueValue),
PropertyDescriptor::Value(str4, trueValue, napi_writable),
PropertyDescriptor::Value(str5, trueValue, napi_enumerable),
Expand All @@ -127,6 +159,21 @@ void DefineProperties(const CallbackInfo& info) {
Napi::String::New(env, "readonlyAccessorWithUserData"), TestGetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast<void*>(holder)),
PropertyDescriptor::Accessor(env, obj,
Napi::String::New(env, "readwriteAccessorWithUserData"), TestGetterWithUserData, TestSetterWithUserData, napi_property_attributes::napi_default, reinterpret_cast<void*>(holder)),

PropertyDescriptor::Accessor<TestGetter>(
Napi::String::New(env, "readonlyAccessorT")),
PropertyDescriptor::Accessor<TestGetter, TestSetter>(
Napi::String::New(env, "readwriteAccessorT")),
PropertyDescriptor::Accessor<TestGetterWithUserData>(
Napi::String::New(env, "readonlyAccessorWithUserDataT"),
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),
PropertyDescriptor::Accessor<
TestGetterWithUserData, TestSetterWithUserData>(
Napi::String::New(env, "readwriteAccessorWithUserDataT"),
napi_property_attributes::napi_default,
reinterpret_cast<void*>(holder)),

PropertyDescriptor::Value(
Napi::String::New(env, "readonlyValue"), trueValue),
PropertyDescriptor::Value(
Expand Down
26 changes: 26 additions & 0 deletions test/object/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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');
Expand All @@ -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');
Expand Down

0 comments on commit 189176b

Please sign in to comment.