Skip to content

feat: add ConstructorTraitTag to allow constructor return overridding #1670

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
9 changes: 6 additions & 3 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4630,9 +4630,11 @@ inline napi_value InstanceWrap<T>::WrappedMethod(
////////////////////////////////////////////////////////////////////////////////

template <typename T>
inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
template <typename ConstructorTraitTag>
inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo,
ConstructorTraitTag) {
napi_env env = callbackInfo.Env();
napi_value wrapper = callbackInfo.This();
napi_value wrapper = ConstructorTraitTag::GetThis(callbackInfo);
napi_status status;
napi_ref ref;
T* instance = static_cast<T*>(this);
Expand Down Expand Up @@ -5026,11 +5028,12 @@ inline napi_value ObjectWrap<T>::ConstructorCallbackWrapper(
Error e = callbackInfo.Env().GetAndClearPendingException();
delete instance;
e.ThrowAsJavaScriptException();
return Object();
} else {
instance->_construction_failed = false;
}
#endif // NODE_ADDON_API_CPP_EXCEPTIONS
return callbackInfo.This();
return instance->Value();
});

return wrapper;
Expand Down
8 changes: 7 additions & 1 deletion napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2359,6 +2359,10 @@ class InstanceWrap {
}
};

struct ConstructorTraitTag {
static napi_value GetThis(const CallbackInfo& info) { return info.This(); }
};

/// Base class to be extended by C++ classes exposed to JavaScript; each C++
/// class instance gets "wrapped" by a JavaScript object that is managed by this
/// class.
Expand Down Expand Up @@ -2389,7 +2393,9 @@ class InstanceWrap {
template <typename T>
class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
public:
ObjectWrap(const CallbackInfo& callbackInfo);
template <typename ConstructorTraitTag = struct ConstructorTraitTag>
ObjectWrap(const CallbackInfo& callbackInfo,
ConstructorTraitTag = ConstructorTraitTag());
virtual ~ObjectWrap();

static T* Unwrap(Object wrapper);
Expand Down
3 changes: 3 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Object InitTypedArray(Env env);
Object InitGlobalObject(Env env);
Object InitObjectWrap(Env env);
Object InitObjectWrapConstructorException(Env env);
Object InitObjectWrapConstructorTraitTag(Env env);
Object InitObjectWrapFunction(Env env);
Object InitObjectWrapRemoveWrap(Env env);
Object InitObjectWrapMultipleInheritance(Env env);
Expand Down Expand Up @@ -168,6 +169,8 @@ Object Init(Env env, Object exports) {
exports.Set("objectwrap", InitObjectWrap(env));
exports.Set("objectwrapConstructorException",
InitObjectWrapConstructorException(env));
exports.Set("objectwrap_constructor_trait_tag",
InitObjectWrapConstructorTraitTag(env));
exports.Set("objectwrap_function", InitObjectWrapFunction(env));
exports.Set("objectwrap_removewrap", InitObjectWrapRemoveWrap(env));
exports.Set("objectwrap_multiple_inheritance",
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
'typedarray.cc',
'objectwrap.cc',
'objectwrap_constructor_exception.cc',
'objectwrap_constructor_trait_tag.cc',
'objectwrap_function.cc',
'objectwrap_removewrap.cc',
'objectwrap_multiple_inheritance.cc',
Expand Down
39 changes: 39 additions & 0 deletions test/objectwrap_constructor_trait_tag.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <napi.h>
#include "test_helper.h"

struct EventTargetConstructorTraitTag {
static napi_value GetThis(const Napi::CallbackInfo& info) {
Napi::Function eventTargetCons =
MaybeUnwrap(info.Env().Global().Get("EventTarget"))
.As<Napi::Function>();
Napi::Object eventTarget = MaybeUnwrap(eventTargetCons.New({}));
return eventTarget;
}
};

class TestOverride : public Napi::ObjectWrap<TestOverride> {
public:
TestOverride(const Napi::CallbackInfo& info)
: Napi::ObjectWrap<TestOverride>(info, EventTargetConstructorTraitTag()) {
}

Napi::Value IsTestOverride(const Napi::CallbackInfo& info) {
TestOverride* self = Unwrap(info.This().As<Napi::Object>());
return Napi::Boolean::New(info.Env(), self != nullptr);
}

static void Initialize(Napi::Env env, Napi::Object exports) {
Napi::Function cons = DefineClass(
env,
"TestOverride",
{InstanceAccessor<&TestOverride::IsTestOverride>("isTestOverride")});

exports.Set("TestOverride", cons);
}
};

Napi::Object InitObjectWrapConstructorTraitTag(Napi::Env env) {
Napi::Object exports = Napi::Object::New(env);
TestOverride::Initialize(env, exports);
return exports;
}
25 changes: 25 additions & 0 deletions test/objectwrap_constructor_trait_tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const assert = require('assert');

const test = bindingName => {
const binding = require(bindingName);
const TestOverride = binding.objectwrap_constructor_trait_tag.TestOverride;
// TODO(legendecas): Expose Object.setPrototypeOf in node-api.
Object.setPrototypeOf(TestOverride, EventTarget);
Object.setPrototypeOf(TestOverride.prototype, EventTarget.prototype);

const test = new TestOverride();
Object.setPrototypeOf(test, TestOverride.prototype);

// Verify that the TestOverride class is an EventTarget instance and has both
// EventTarget brand and TestOverride brand.
assert(test instanceof EventTarget);
assert(test instanceof TestOverride);
// Verify TestOverride brand.
assert.strictEqual(test.isTestOverride, true);
// Verify EventTarget brand.
test.addEventListener('test', () => {});
};

module.exports = require('./common').runTestWithBindingPath(test);
Loading