Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: add templated function factories #608

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion doc/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Value Fn(const CallbackInfo& info) {
}

Object Init(Env env, Object exports) {
exports.Set(String::New(env, "fn"), Function::New(env, Fn));
exports.Set(String::New(env, "fn"), Function::New<Fn>(env));
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
Expand All @@ -47,6 +47,27 @@ and in general in situations which don't have an existing JavaScript function on
the stack. The `Call` method is used when there is already a JavaScript function
on the stack (for example when running a native method called from JavaScript).

## Type definitions

### Napi::Function::VoidCallback

This is the type describing a callback returning `void` that will be invoked
from JavaScript.

```cpp
typedef void (*VoidCallback)(const Napi::CallbackInfo& info);
```

### Napi::Function::Callback

This is the type describing a callback returning a value that will be invoked
from JavaScript.


```cpp
typedef Value (*Callback)(const Napi::CallbackInfo& info);
```

## Methods

### Constructor
Expand Down Expand Up @@ -74,6 +95,86 @@ Returns a non-empty `Napi::Function` instance.

Creates an instance of a `Napi::Function` object.

```cpp
template <Napi::VoidCallback cb>
static Napi::Function New(napi_env env,
const char* utf8name = nullptr,
void* data = nullptr);
```

- `[template] cb`: The native function to invoke when the JavaScript function is
invoked.
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
- `[in] utf8name`: Null-terminated string to be used as the name of the function.
- `[in] data`: User-provided data context. This will be passed back into the
function when invoked later.

Returns an instance of a `Napi::Function` object.

### New

Creates an instance of a `Napi::Function` object.

```cpp
template <Napi::Callback cb>
static Napi::Function New(napi_env env,
const char* utf8name = nullptr,
void* data = nullptr);
```

- `[template] cb`: The native function to invoke when the JavaScript function is
invoked.
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
- `[in] utf8name`: Null-terminated string to be used as the name of the function.
- `[in] data`: User-provided data context. This will be passed back into the
function when invoked later.

Returns an instance of a `Napi::Function` object.

### New

Creates an instance of a `Napi::Function` object.

```cpp
template <Napi::VoidCallback cb>
static Napi::Function New(napi_env env,
const std::string& utf8name,
void* data = nullptr);
```

- `[template] cb`: The native function to invoke when the JavaScript function is
invoked.
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
- `[in] utf8name`: String to be used as the name of the function.
- `[in] data`: User-provided data context. This will be passed back into the
function when invoked later.

Returns an instance of a `Napi::Function` object.

### New

Creates an instance of a `Napi::Function` object.

```cpp
template <Napi::Callback cb>
static Napi::Function New(napi_env env,
const std::string& utf8name,
void* data = nullptr);
```

- `[template] cb`: The native function to invoke when the JavaScript function is
invoked.
- `[in] env`: The `napi_env` environment in which to construct the `Napi::Function` object.
- `[in] utf8name`: String to be used as the name of the function.
- `[in] data`: User-provided data context. This will be passed back into the
function when invoked later.

Returns an instance of a `Napi::Function` object.

### New

Creates an instance of a `Napi::Function` object.

```cpp
template <typename Callable>
static Napi::Function Napi::Function::New(napi_env env, Callable cb, const char* utf8name = nullptr, void* data = nullptr);
Expand Down
45 changes: 45 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,51 @@ CreateFunction(napi_env env,
return status;
}

template <Function::VoidCallback cb>
inline Function Function::New(napi_env env, const char* utf8name, void* data) {
napi_value result = nullptr;
napi_status status = napi_create_function(
env, utf8name, NAPI_AUTO_LENGTH,
[](napi_env env, napi_callback_info info) {
CallbackInfo callbackInfo(env, info);
return details::WrapCallback([&] {
cb(callbackInfo);
return nullptr;
});
}, data, &result);
NAPI_THROW_IF_FAILED(env, status, Function());
return Function(env, result);
}

template <Function::Callback cb>
inline Function Function::New(napi_env env, const char* utf8name, void* data) {
napi_value result = nullptr;
napi_status status = napi_create_function(
env, utf8name, NAPI_AUTO_LENGTH,
[](napi_env env, napi_callback_info info) {
CallbackInfo callbackInfo(env, info);
return details::WrapCallback([&] {
return cb(callbackInfo);
});
}, data, &result);
NAPI_THROW_IF_FAILED(env, status, Function());
return Function(env, result);
}

template <Function::VoidCallback cb>
inline Function Function::New(napi_env env,
const std::string& utf8name,
void* data) {
return Function::New<cb>(env, utf8name.c_str(), data);
}

template <Function::Callback cb>
inline Function Function::New(napi_env env,
const std::string& utf8name,
void* data) {
return Function::New<cb>(env, utf8name.c_str(), data);
}

template <typename Callable>
inline Function Function::New(napi_env env,
Callable cb,
Expand Down
23 changes: 23 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,29 @@ namespace Napi {

class Function : public Object {
public:
typedef void (*VoidCallback)(const CallbackInfo& info);
typedef Value (*Callback)(const CallbackInfo& info);

template <VoidCallback cb>
static Function New(napi_env env,
const char* utf8name = nullptr,
void* data = nullptr);

template <Callback cb>
static Function New(napi_env env,
const char* utf8name = nullptr,
void* data = nullptr);

template <VoidCallback cb>
static Function New(napi_env env,
const std::string& utf8name,
void* data = nullptr);

template <Callback cb>
static Function New(napi_env env,
const std::string& utf8name,
void* data = nullptr);

/// Callable must implement operator() accepting a const CallbackInfo&
/// and return either void or Value.
template <typename Callable>
Expand Down
27 changes: 26 additions & 1 deletion test/function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ void IsConstructCall(const CallbackInfo& info) {
} // end anonymous namespace

Object InitFunction(Env env) {
Object result = Object::New(env);
Object exports = Object::New(env);
exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback");
exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback"));
Expand All @@ -120,5 +121,29 @@ Object InitFunction(Env env) {
exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs);
exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector);
exports["isConstructCall"] = Function::New(env, IsConstructCall);
return exports;
result["plain"] = exports;

exports = Object::New(env);
exports["voidCallback"] = Function::New<VoidCallback>(env, "voidCallback");
exports["valueCallback"] =
Function::New<ValueCallback>(env, std::string("valueCallback"));
exports["voidCallbackWithData"] =
Function::New<VoidCallbackWithData>(env, nullptr, &testData);
exports["valueCallbackWithData"] =
Function::New<ValueCallbackWithData>(env, nullptr, &testData);
exports["callWithArgs"] = Function::New<CallWithArgs>(env);
exports["callWithVector"] = Function::New<CallWithVector>(env);
exports["callWithReceiverAndArgs"] =
Function::New<CallWithReceiverAndArgs>(env);
exports["callWithReceiverAndVector"] =
Function::New<CallWithReceiverAndVector>(env);
exports["callWithInvalidReceiver"] =
Function::New<CallWithInvalidReceiver>(env);
exports["callConstructorWithArgs"] =
Function::New<CallConstructorWithArgs>(env);
exports["callConstructorWithVector"] =
Function::New<CallConstructorWithVector>(env);
exports["isConstructCall"] = Function::New<IsConstructCall>(env);
result["templated"] = exports;
return result;
}
36 changes: 19 additions & 17 deletions test/function.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
const buildType = process.config.target_defaults.default_configuration;
const assert = require('assert');

test(require(`./build/${buildType}/binding.node`));
test(require(`./build/${buildType}/binding_noexcept.node`));
test(require(`./build/${buildType}/binding.node`).function.plain);
test(require(`./build/${buildType}/binding_noexcept.node`).function.plain);
test(require(`./build/${buildType}/binding.node`).function.templated);
test(require(`./build/${buildType}/binding_noexcept.node`).function.templated);

function test(binding) {
let obj = {};
assert.deepStrictEqual(binding.function.voidCallback(obj), undefined);
assert.deepStrictEqual(binding.voidCallback(obj), undefined);
assert.deepStrictEqual(obj, { "foo": "bar" });

assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" });
assert.deepStrictEqual(binding.valueCallback(), { "foo": "bar" });

let args = null;
let ret = null;
Expand All @@ -25,50 +27,50 @@ function test(binding) {
}

ret = 4;
assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4);
assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4);
assert.strictEqual(receiver, undefined);
assert.deepStrictEqual(args, [ 1, 2, 3 ]);

ret = 5;
assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5);
assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5);
assert.strictEqual(receiver, undefined);
assert.deepStrictEqual(args, [ 2, 3, 4 ]);

ret = 6;
assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6);
assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6);
assert.deepStrictEqual(receiver, obj);
assert.deepStrictEqual(args, [ 3, 4, 5 ]);

ret = 7;
assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7);
assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7);
assert.deepStrictEqual(receiver, obj);
assert.deepStrictEqual(args, [ 4, 5, 6 ]);

assert.throws(() => {
binding.function.callWithInvalidReceiver();
binding.callWithInvalidReceiver();
}, /Invalid (pointer passed as )?argument/);

obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7);
obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7);
assert(obj instanceof testConstructor);
assert.deepStrictEqual(args, [ 5, 6, 7 ]);

obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8);
obj = binding.callConstructorWithVector(testConstructor, 6, 7, 8);
assert(obj instanceof testConstructor);
assert.deepStrictEqual(args, [ 6, 7, 8 ]);

obj = {};
assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined);
assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined);
assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 });

assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 });
assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 });

assert.equal(binding.function.voidCallback.name, 'voidCallback');
assert.equal(binding.function.valueCallback.name, 'valueCallback');
assert.equal(binding.voidCallback.name, 'voidCallback');
assert.equal(binding.valueCallback.name, 'valueCallback');

let testConstructCall = undefined;
binding.function.isConstructCall((result) => { testConstructCall = result; });
binding.isConstructCall((result) => { testConstructCall = result; });
assert.ok(!testConstructCall);
new binding.function.isConstructCall((result) => { testConstructCall = result; });
new binding.isConstructCall((result) => { testConstructCall = result; });
assert.ok(testConstructCall);

// TODO: Function::MakeCallback tests
Expand Down