From 977679d3bc0add0a5335acba840114668f07fb80 Mon Sep 17 00:00:00 2001 From: dave-365 Date: Thu, 5 Nov 2026 14:21:46 +0530 Subject: [PATCH 1/4] feat: enforce callback signatures with C++ Concepts (#1746) --- napi-inl.deprecated.h | 126 ++++++++------ napi-inl.h | 169 +++++++++++-------- napi.h | 384 ++++++++++++++++++++++++++---------------- test/binding.cc | 2 + test/binding.gyp | 16 ++ test/common/index.js | 14 +- test/concepts.cc | 138 +++++++++++++++ test/concepts.js | 36 ++++ 8 files changed, 618 insertions(+), 267 deletions(-) create mode 100644 test/concepts.cc create mode 100644 test/concepts.js diff --git a/napi-inl.deprecated.h b/napi-inl.deprecated.h index 3ddbb2efa..6b79a9d41 100644 --- a/napi-inl.deprecated.h +++ b/napi-inl.deprecated.h @@ -6,11 +6,12 @@ //////////////////////////////////////////////////////////////////////////////// template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { using CbData = details::CallbackData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({getter, nullptr}); @@ -26,20 +27,22 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - napi_value name, - Getter getter, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes, + void* /*data*/) { using CbData = details::CallbackData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({getter, nullptr}); @@ -55,6 +58,7 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template +NAPI_REQUIRES(concepts::GetterCallable) inline PropertyDescriptor PropertyDescriptor::Accessor( Name name, Getter getter, napi_property_attributes attributes, void* data) { napi_value nameValue = name; @@ -62,12 +66,14 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { using CbData = details::AccessorCallbackData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({getter, setter, nullptr}); @@ -83,22 +89,26 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { return Accessor(utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* /*data*/) { using CbData = details::AccessorCallbackData; // TODO: Delete when the function is destroyed auto callbackData = new CbData({getter, setter, nullptr}); @@ -114,23 +124,26 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { napi_value nameValue = name; return PropertyDescriptor::Accessor( nameValue, getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function( - const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); using CbData = details::CallbackData; // TODO: Delete when the function is destroyed @@ -147,20 +160,22 @@ inline PropertyDescriptor PropertyDescriptor::Function( } template -inline PropertyDescriptor PropertyDescriptor::Function( - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { return Function(utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function( - napi_value name, - Callable cb, - napi_property_attributes attributes, - void* /*data*/) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(napi_value name, + Callable cb, + napi_property_attributes attributes, + void* /*data*/) { using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); using CbData = details::CallbackData; // TODO: Delete when the function is destroyed @@ -177,10 +192,11 @@ inline PropertyDescriptor PropertyDescriptor::Function( } template +NAPI_REQUIRES(concepts::CallbackCallable) inline PropertyDescriptor PropertyDescriptor::Function( Name name, Callable cb, napi_property_attributes attributes, void* data) { napi_value nameValue = name; return PropertyDescriptor::Function(nameValue, cb, attributes, data); } -#endif // !SRC_NAPI_INL_DEPRECATED_H_ +#endif // SRC_NAPI_INL_DEPRECATED_H_ diff --git a/napi-inl.h b/napi-inl.h index 59f1ca14e..9424fd045 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1904,6 +1904,7 @@ inline MaybeOrValue Object::InstanceOf( } template +NAPI_REQUIRES(concepts::FinalizerWithData) inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { details::FinalizeData* finalizeData = new details::FinalizeData( @@ -1918,6 +1919,7 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { } template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint) const { @@ -2050,6 +2052,7 @@ inline External External::New(napi_env env, T* data) { template template +NAPI_REQUIRES(concepts::FinalizerWithData) inline External External::New(napi_env env, T* data, Finalizer finalizeCallback) { @@ -2072,6 +2075,7 @@ inline External External::New(napi_env env, template template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline External External::New(napi_env env, T* data, Finalizer finalizeCallback, @@ -2231,6 +2235,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, } template +NAPI_REQUIRES(concepts::FinalizerWithData) inline ArrayBuffer ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength, @@ -2255,6 +2260,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, } template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline ArrayBuffer ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength, @@ -2824,6 +2830,7 @@ inline Function Function::New(napi_env env, } template +NAPI_REQUIRES(concepts::CallbackCallable) inline Function Function::New(napi_env env, Callable cb, const char* utf8name, @@ -2844,6 +2851,7 @@ inline Function Function::New(napi_env env, } template +NAPI_REQUIRES(concepts::CallbackCallable) inline Function Function::New(napi_env env, Callable cb, const std::string& utf8name, @@ -3130,6 +3138,7 @@ inline Buffer Buffer::New(napi_env env, T* data, size_t length) { template template +NAPI_REQUIRES(concepts::FinalizerWithData) inline Buffer Buffer::New(napi_env env, T* data, size_t length, @@ -3154,6 +3163,7 @@ inline Buffer Buffer::New(napi_env env, template template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline Buffer Buffer::New(napi_env env, T* data, size_t length, @@ -3197,6 +3207,7 @@ inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { template template +NAPI_REQUIRES(concepts::FinalizerWithData) inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length, @@ -3231,6 +3242,7 @@ inline Buffer Buffer::NewOrCopy(napi_env env, template template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length, @@ -4391,13 +4403,14 @@ PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { using CbData = details::CallbackData; auto callbackData = new CbData({getter, data}); @@ -4418,24 +4431,26 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { return Accessor(env, object, utf8name.c_str(), getter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::GetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { using CbData = details::CallbackData; auto callbackData = new CbData({getter, data}); @@ -4456,14 +4471,16 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { using CbData = details::AccessorCallbackData; auto callbackData = new CbData({getter, setter, data}); @@ -4484,27 +4501,31 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { return Accessor( env, object, utf8name.c_str(), getter, setter, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) +inline PropertyDescriptor + PropertyDescriptor::Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { using CbData = details::AccessorCallbackData; auto callbackData = new CbData({getter, setter, data}); @@ -4525,13 +4546,14 @@ inline PropertyDescriptor PropertyDescriptor::Accessor( } template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object /*object*/, - const char* utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { return PropertyDescriptor({utf8name, nullptr, nullptr, @@ -4543,24 +4565,26 @@ inline PropertyDescriptor PropertyDescriptor::Function( } template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { return Function(env, object, utf8name.c_str(), cb, attributes, data); } template -inline PropertyDescriptor PropertyDescriptor::Function( - Napi::Env env, - Napi::Object /*object*/, - Name name, - Callable cb, - napi_property_attributes attributes, - void* data) { +NAPI_REQUIRES(concepts::CallbackCallable) +inline PropertyDescriptor + PropertyDescriptor::Function(Napi::Env env, + Napi::Object /*object*/, + Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { return PropertyDescriptor({nullptr, name, nullptr, @@ -6344,6 +6368,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, // static template +NAPI_REQUIRES(concepts::Finalizer) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, ResourceString resourceName, @@ -6363,6 +6388,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, template +NAPI_REQUIRES(concepts::FinalizerWithData) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, ResourceString resourceName, @@ -6382,6 +6408,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, // static template +NAPI_REQUIRES(concepts::FinalizerWithData) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, ResourceString resourceName, @@ -6404,6 +6431,8 @@ template +NAPI_REQUIRES( + concepts::FinalizerWithHint) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, ResourceString resourceName, @@ -6461,6 +6490,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, // static template +NAPI_REQUIRES(concepts::Finalizer) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -6484,6 +6514,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, template +NAPI_REQUIRES(concepts::FinalizerWithData) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -6507,6 +6538,7 @@ inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, // static template +NAPI_REQUIRES(concepts::FinalizerWithData) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -6534,6 +6566,8 @@ template +NAPI_REQUIRES( + concepts::FinalizerWithHint) inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, const Function& callback, const Object& resource, @@ -7209,6 +7243,7 @@ bool Env::CleanupHook::IsEmpty() const { #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER template +NAPI_REQUIRES(concepts::Finalizer) inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { using T = void*; details::FinalizeData* finalizeData = @@ -7228,6 +7263,7 @@ inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { } template +NAPI_REQUIRES(concepts::FinalizerWithData) inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, T* data) const { details::FinalizeData* finalizeData = @@ -7247,6 +7283,7 @@ inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, } template +NAPI_REQUIRES(concepts::FinalizerWithHint) inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, T* data, Hint* finalizeHint) const { diff --git a/napi.h b/napi.h index 6b4692c7d..8b5bb218f 100644 --- a/napi.h +++ b/napi.h @@ -24,6 +24,16 @@ #include #include +#if (defined(__cpp_concepts) && __cpp_concepts >= 201907L) || \ + (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#if defined(__has_include) +#if __has_include() +#include +#define NAPI_HAS_CPP20_CONCEPTS 1 +#endif +#endif +#endif + // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known // good version) #if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 @@ -258,6 +268,43 @@ using BigUint64Array = /// (init) function. using ModuleRegisterCallback = Object (*)(Env env, Object exports); +#if defined(NAPI_HAS_CPP20_CONCEPTS) +namespace concepts { + +template +concept CallbackCallable = + std::invocable && + (std::same_as, void> || + std::convertible_to, + napi_value>); + +template +concept GetterCallable = + std::invocable && + (std::convertible_to, + napi_value>); + +template +concept SetterCallable = + std::invocable && + (std::same_as, void>); + +template +concept Finalizer = std::invocable; + +template +concept FinalizerWithData = std::invocable; + +template +concept FinalizerWithHint = std::invocable; + +} // namespace concepts + +#define NAPI_REQUIRES(...) requires __VA_ARGS__ +#else +#define NAPI_REQUIRES(...) +#endif + class MemoryManagement; /// A simple Maybe type, representing an object which may or may not have a @@ -426,15 +473,18 @@ class BasicEnv { #ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER // FinalizerType must implement `void operator()(Env env)`. template + NAPI_REQUIRES(concepts::Finalizer) inline void PostFinalizer(FinalizerType finalizeCallback) const; // FinalizerType must implement `void operator()(Env env, T* data)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; // FinalizerType must implement `void operator()(Env env, T* data, // Hint* hint)`. template + NAPI_REQUIRES(concepts::FinalizerWithHint) inline void PostFinalizer(FinalizerType finalizeCallback, T* data, Hint* finalizeHint) const; @@ -1166,11 +1216,13 @@ class Object : public TypeTaggable { // Finalizer must implement `void operator()(Env env, T* data)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; // Finalizer must implement `void operator()(Env env, T* data, // Hint* hint)`. template + NAPI_REQUIRES(concepts::FinalizerWithHint) inline void AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint) const; @@ -1216,10 +1268,12 @@ class External : public TypeTaggable { // Finalizer must implement `void operator()(Env env, T* data)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) static External New(napi_env env, T* data, Finalizer finalizeCallback); // Finalizer must implement `void operator()(Env env, T* data, // Hint* hint)`. template + NAPI_REQUIRES(concepts::FinalizerWithHint) static External New(napi_env env, T* data, Finalizer finalizeCallback, @@ -1334,22 +1388,24 @@ class ArrayBuffer : public Object { /// Creates a new ArrayBuffer instance, using an external buffer with /// specified byte length. template - static ArrayBuffer New( - napi_env env, ///< Node-API environment - void* externalData, ///< Pointer to the external buffer to be used by - ///< the array - size_t byteLength, ///< Length of the external buffer to be used by the - ///< array, - /// in bytes - Finalizer finalizeCallback ///< Function to be called when the array - ///< buffer is destroyed; - /// must implement `void operator()(Env env, - /// void* externalData)` - ); + NAPI_REQUIRES(concepts::FinalizerWithData) + static ArrayBuffer + New(napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by + ///< the array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData)` + ); /// Creates a new ArrayBuffer instance, using an external buffer with /// specified byte length. template + NAPI_REQUIRES(concepts::FinalizerWithHint) static ArrayBuffer New( napi_env env, ///< Node-API environment void* externalData, ///< Pointer to the external buffer to be used by @@ -1674,6 +1730,7 @@ class Function : public Object { /// Callable must implement operator() accepting a const CallbackInfo& /// and return either void or Value. template + NAPI_REQUIRES(concepts::CallbackCallable) static Function New(napi_env env, Callable cb, const char* utf8name = nullptr, @@ -1681,6 +1738,7 @@ class Function : public Object { /// Callable must implement operator() accepting a const CallbackInfo& /// and return either void or Value. template + NAPI_REQUIRES(concepts::CallbackCallable) static Function New(napi_env env, Callable cb, const std::string& utf8name, @@ -1769,6 +1827,7 @@ class Buffer : public Uint8Array { // Finalizer must implement `void operator()(Env env, T* data)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) static Buffer New(napi_env env, T* data, size_t length, @@ -1776,6 +1835,7 @@ class Buffer : public Uint8Array { // Finalizer must implement `void operator()(Env env, T* data, // Hint* hint)`. template + NAPI_REQUIRES(concepts::FinalizerWithHint) static Buffer New(napi_env env, T* data, size_t length, @@ -1786,6 +1846,7 @@ class Buffer : public Uint8Array { static Buffer NewOrCopy(napi_env env, T* data, size_t length); // Finalizer must implement `void operator()(Env env, T* data)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) static Buffer NewOrCopy(napi_env env, T* data, size_t length, @@ -1793,6 +1854,7 @@ class Buffer : public Uint8Array { // Finalizer must implement `void operator()(Env env, T* data, // Hint* hint)`. template + NAPI_REQUIRES(concepts::FinalizerWithHint) static Buffer NewOrCopy(napi_env env, T* data, size_t length, @@ -2175,81 +2237,97 @@ class PropertyDescriptor { #ifndef NODE_ADDON_API_DISABLE_DEPRECATED template - static PropertyDescriptor Accessor( - const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - napi_value name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - napi_value name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - napi_value name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); #endif // !NODE_ADDON_API_DISABLE_DEPRECATED template @@ -2289,80 +2367,92 @@ class PropertyDescriptor { void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::GetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Accessor( - Napi::Env env, - Napi::Object object, - Name name, - Getter getter, - Setter setter, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES( + concepts::GetterCallable&& concepts::SetterCallable) + static PropertyDescriptor + Accessor(Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - const char* utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - const std::string& utf8name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); template - static PropertyDescriptor Function( - Napi::Env env, - Napi::Object object, - Name name, - Callable cb, - napi_property_attributes attributes = napi_default, - void* data = nullptr); + NAPI_REQUIRES(concepts::CallbackCallable) + static PropertyDescriptor + Function(Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor Value( const char* utf8name, napi_value value, @@ -2890,6 +2980,7 @@ class ThreadSafeFunction { // This API may only be called from the main thread. // Finalizer must implement `void operator()(Env env)`. template + NAPI_REQUIRES(concepts::Finalizer) static ThreadSafeFunction New(napi_env env, const Function& callback, ResourceString resourceName, @@ -2903,6 +2994,7 @@ class ThreadSafeFunction { template + NAPI_REQUIRES(concepts::FinalizerWithData) static ThreadSafeFunction New(napi_env env, const Function& callback, ResourceString resourceName, @@ -2915,6 +3007,7 @@ class ThreadSafeFunction { // Finalizer must implement // `void operator()(Env env, ContextType* context)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) static ThreadSafeFunction New(napi_env env, const Function& callback, ResourceString resourceName, @@ -2930,6 +3023,8 @@ class ThreadSafeFunction { typename ContextType, typename Finalizer, typename FinalizerDataType> + NAPI_REQUIRES( + concepts::FinalizerWithHint) static ThreadSafeFunction New(napi_env env, const Function& callback, ResourceString resourceName, @@ -2961,6 +3056,7 @@ class ThreadSafeFunction { // This API may only be called from the main thread. // Finalizer must implement `void operator()(Env env)`. template + NAPI_REQUIRES(concepts::Finalizer) static ThreadSafeFunction New(napi_env env, const Function& callback, const Object& resource, @@ -2975,6 +3071,7 @@ class ThreadSafeFunction { template + NAPI_REQUIRES(concepts::FinalizerWithData) static ThreadSafeFunction New(napi_env env, const Function& callback, const Object& resource, @@ -2988,6 +3085,7 @@ class ThreadSafeFunction { // Finalizer must implement // `void operator()(Env env, ContextType* context)`. template + NAPI_REQUIRES(concepts::FinalizerWithData) static ThreadSafeFunction New(napi_env env, const Function& callback, const Object& resource, @@ -3004,6 +3102,8 @@ class ThreadSafeFunction { typename ContextType, typename Finalizer, typename FinalizerDataType> + NAPI_REQUIRES( + concepts::FinalizerWithHint) static ThreadSafeFunction New(napi_env env, const Function& callback, const Object& resource, diff --git a/test/binding.cc b/test/binding.cc index fa651cc13..741b6a807 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -26,6 +26,7 @@ Object InitBufferNoExternal(Env env); #if (NAPI_VERSION > 2) Object InitCallbackScope(Env env); #endif +Object InitConcepts(Env env); #if (NAPI_VERSION > 4) Object InitDate(Env env); #endif @@ -118,6 +119,7 @@ Object Init(Env env, Object exports) { #if (NAPI_VERSION > 2) exports.Set("callbackscope", InitCallbackScope(env)); #endif + exports.Set("concepts", InitConcepts(env)); exports.Set("callbackInfo", InitCallbackInfo(env)); exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 9ff334b64..41cea98c1 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -23,6 +23,7 @@ 'buffer_no_external.cc', 'buffer.cc', 'callbackscope.cc', + 'concepts.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', 'env_cleanup.cc', @@ -118,6 +119,21 @@ 'sources': ['>@(build_sources)'], 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] }, + { + 'target_name': 'binding_cpp20', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'], + 'cflags_cc': ['-std=c++20'], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'AdditionalOptions': ['/std:c++20'] + } + }, + 'xcode_settings': { + 'CLANG_CXX_LANGUAGE_STANDARD': 'c++20' + } + }, { 'target_name': 'binding_except_all', 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], diff --git a/test/common/index.js b/test/common/index.js index 2469151ef..0fe628272 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -161,12 +161,15 @@ exports.whichBuildType = whichBuildType; exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await Promise.resolve(test(require(item), { bindingPath: item })) @@ -177,12 +180,15 @@ exports.runTest = async function (test, buildType, buildPathRoot = process.env.B exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); + const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) - ].map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) + ].filter(it => fs.existsSync(path.resolve(__dirname, it))) + .map(it => require.resolve(it)); for (const item of bindings) { await test(item); diff --git a/test/concepts.cc b/test/concepts.cc new file mode 100644 index 000000000..4ad260b70 --- /dev/null +++ b/test/concepts.cc @@ -0,0 +1,138 @@ +#include "napi.h" + +using namespace Napi; + +#if defined(NAPI_HAS_CPP20_CONCEPTS) + +// Test CallbackCallable concept +static_assert(concepts::CallbackCallable); +static_assert(concepts::CallbackCallable< + decltype([](const CallbackInfo& /*info*/) -> void {})>); +static_assert(concepts::CallbackCallable); + +// Rejections: +// Non-copyable CallbackInfo passed by value +static_assert(!concepts::CallbackCallable); +static_assert(!concepts::CallbackCallable); +static_assert(!concepts::CallbackCallable); +static_assert(!concepts::CallbackCallable< + decltype([](const CallbackInfo& /*info*/, int /*extra*/) {})>); +static_assert( + !concepts::CallbackCallable< + decltype([](const CallbackInfo& /*info*/) -> int { return 42; })>); +static_assert(!concepts::CallbackCallable); +static_assert(!concepts::CallbackCallable); + +// Test GetterCallable concept +static_assert(concepts::GetterCallable); +static_assert(concepts::GetterCallable); +static_assert(!concepts::GetterCallable< + decltype([](const CallbackInfo& /*info*/) -> void {})>); +static_assert(!concepts::GetterCallable); +static_assert(!concepts::GetterCallable); + +// Test SetterCallable concept +static_assert( + concepts::SetterCallable); +static_assert(concepts::SetterCallable< + decltype([](const CallbackInfo& /*info*/) -> void {})>); +static_assert(!concepts::SetterCallable); +static_assert(!concepts::SetterCallable); +static_assert(!concepts::SetterCallable); + +// Test Finalizer concept +static_assert(concepts::Finalizer); +static_assert(!concepts::Finalizer); +static_assert(!concepts::Finalizer); +static_assert( + !concepts::Finalizer); +static_assert(!concepts::Finalizer); + +// Test FinalizerWithData concept +static_assert( + concepts::FinalizerWithData); +static_assert( + concepts::FinalizerWithData); +static_assert( + !concepts::FinalizerWithData); +static_assert(!concepts::FinalizerWithData); +static_assert(!concepts::FinalizerWithData); + +// Test FinalizerWithHint concept +static_assert(concepts::FinalizerWithHint< + decltype([](Env /*env*/, int* /*data*/, char* /*hint*/) {}), + int, + char>); +static_assert( + !concepts::FinalizerWithHint); +static_assert(!concepts::FinalizerWithHint< + decltype([](Env /*env*/, int* /*data*/, int* /*hint*/) {}), + int, + char>); + +#endif // defined(NAPI_HAS_CPP20_CONCEPTS) + +Object InitConcepts(Env env) { + Object exports = Object::New(env); + +#if defined(NAPI_HAS_CPP20_CONCEPTS) + exports["hasConcepts"] = Boolean::New(env, true); +#else + exports["hasConcepts"] = Boolean::New(env, false); +#endif + + exports["testFunction"] = Function::New(env, [](const CallbackInfo& info) { + return String::New(info.Env(), "concept callback ok"); + }); + + Object obj = Object::New(env); + obj.DefineProperty(PropertyDescriptor::Accessor( + env, + obj, + "testProp", + [](const CallbackInfo& info) { return Number::New(info.Env(), 42); }, + [](const CallbackInfo& /*info*/) {})); + exports["testAccessor"] = obj; + + Object fnObj = Object::New(env); + fnObj.DefineProperty(PropertyDescriptor::Function( + env, fnObj, "testMethod", [](const CallbackInfo& info) { + return Boolean::New(info.Env(), true); + })); + exports["testFunctionDescriptor"] = fnObj; + + static int finalizerData = 100; + Object finalizerObj = Object::New(env); + finalizerObj.AddFinalizer([](Env /*env*/, int* data) { *data = 200; }, + &finalizerData); + exports["testFinalizer"] = finalizerObj; + + static char abData[16] = {0}; + ArrayBuffer ab = ArrayBuffer::New( + env, abData, sizeof(abData), [](Env /*env*/, void* /*data*/) {}); + exports["testArrayBuffer"] = ab; + + static uint8_t bufData[8] = {0}; + Buffer buf = Buffer::New( + env, bufData, sizeof(bufData), [](Env /*env*/, uint8_t* /*data*/) {}); + exports["testBuffer"] = buf; + + return exports; +} diff --git a/test/concepts.js b/test/concepts.js new file mode 100644 index 000000000..7e3202f1c --- /dev/null +++ b/test/concepts.js @@ -0,0 +1,36 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + +function test (binding, context) { + assert.ok(binding.concepts); + assert.strictEqual(typeof binding.concepts.hasConcepts, 'boolean'); + + if (context && context.bindingPath && context.bindingPath.includes('binding_cpp20')) { + assert.strictEqual(binding.concepts.hasConcepts, true); + } + + // Verify function created with concept-constrained CallbackCallable + assert.strictEqual(binding.concepts.testFunction(), 'concept callback ok'); + + // Verify property descriptor created with concept-constrained GetterCallable & SetterCallable + assert.strictEqual(binding.concepts.testAccessor.testProp, 42); + binding.concepts.testAccessor.testProp = 100; + assert.strictEqual(binding.concepts.testAccessor.testProp, 42); + + // Verify function descriptor created with concept-constrained CallbackCallable + assert.strictEqual(binding.concepts.testFunctionDescriptor.testMethod(), true); + + // Verify finalizer-attached object + assert.ok(binding.concepts.testFinalizer); + + // Verify array buffer with concept-constrained finalizer + assert.ok(binding.concepts.testArrayBuffer instanceof ArrayBuffer); + assert.strictEqual(binding.concepts.testArrayBuffer.byteLength, 16); + + // Verify buffer with concept-constrained finalizer + assert.ok(Buffer.isBuffer(binding.concepts.testBuffer)); + assert.strictEqual(binding.concepts.testBuffer.length, 8); +} From 8442865748296db48a1a6e637bc885641e06aa70 Mon Sep 17 00:00:00 2001 From: vasudevan Date: Sat, 7 Nov 2026 11:59:47 +0530 Subject: [PATCH 2/4] test(concepts): isolate binding_cpp20 target and test runner --- test/binding.gyp | 9 ++++++--- test/common/index.js | 14 ++++---------- test/concepts.cc | 10 ++++++++++ test/concepts.js | 19 +++++++++++++++++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/test/binding.gyp b/test/binding.gyp index 41cea98c1..7b53b154e 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -93,6 +93,9 @@ 'build_sources_type_check': [ 'value_type_cast.cc' ], + 'build_sources_cpp20': [ + 'concepts.cc' + ], 'want_coverage': '@(build_sources)'], - 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'], + 'sources': ['>@(build_sources_cpp20)'], + 'defines': ['BUILDING_CPP20_MODULE'], 'cflags_cc': ['-std=c++20'], 'msvs_settings': { 'VCCLCompilerTool': { - 'AdditionalOptions': ['/std:c++20'] + 'AdditionalOptions': ['/std:c++20', '/Zc:__cplusplus'] } }, 'xcode_settings': { diff --git a/test/common/index.js b/test/common/index.js index 0fe628272..2469151ef 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -161,15 +161,12 @@ exports.whichBuildType = whichBuildType; exports.runTest = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); - const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) - ].filter(it => fs.existsSync(path.resolve(__dirname, it))) - .map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); for (const item of bindings) { await Promise.resolve(test(require(item), { bindingPath: item })) @@ -180,15 +177,12 @@ exports.runTest = async function (test, buildType, buildPathRoot = process.env.B exports.runTestWithBindingPath = async function (test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || await whichBuildType(); - const fs = require('fs'); const bindings = [ path.join(buildPathRoot, `../build/${buildType}/binding.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`), - path.join(buildPathRoot, `../build/${buildType}/binding_cpp20.node`) - ].filter(it => fs.existsSync(path.resolve(__dirname, it))) - .map(it => require.resolve(it)); + path.join(buildPathRoot, `../build/${buildType}/binding_custom_namespace.node`) + ].map(it => require.resolve(it)); for (const item of bindings) { await test(item); diff --git a/test/concepts.cc b/test/concepts.cc index 4ad260b70..c22ba119b 100644 --- a/test/concepts.cc +++ b/test/concepts.cc @@ -136,3 +136,13 @@ Object InitConcepts(Env env) { return exports; } + +#if defined(BUILDING_CPP20_MODULE) +static Object InitConceptsModule(Env env, Object exports) { + exports.Set("concepts", InitConcepts(env)); + return exports; +} + +NODE_API_MODULE(addon, InitConceptsModule) +#endif + diff --git a/test/concepts.js b/test/concepts.js index 7e3202f1c..68dc6c0d1 100644 --- a/test/concepts.js +++ b/test/concepts.js @@ -1,8 +1,23 @@ 'use strict'; const assert = require('assert'); - -module.exports = require('./common').runTest(test); +const fs = require('fs'); +const path = require('path'); +const common = require('./common'); + +module.exports = common.runTestWithBuildType(async (buildType) => { + await common.runTest(test, buildType); + + const cpp20BindingPath = path.resolve( + __dirname, + `../build/${buildType}/binding_cpp20.node` + ); + if (fs.existsSync(cpp20BindingPath)) { + await Promise.resolve( + test(require(cpp20BindingPath), { bindingPath: cpp20BindingPath }) + ).finally(common.mustCall()); + } +}); function test (binding, context) { assert.ok(binding.concepts); From 386399c1be303737f4adf0908a3c4488bb63569c Mon Sep 17 00:00:00 2001 From: vasudevan Date: Sun, 8 Nov 2026 11:55:37 +0530 Subject: [PATCH 3/4] style: fix trailing newline in test/concepts.cc --- test/concepts.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/test/concepts.cc b/test/concepts.cc index c22ba119b..29285bc0f 100644 --- a/test/concepts.cc +++ b/test/concepts.cc @@ -145,4 +145,3 @@ static Object InitConceptsModule(Env env, Object exports) { NODE_API_MODULE(addon, InitConceptsModule) #endif - From 7c03fa31265115c42a24fe35ab5f2fbbac0453a9 Mon Sep 17 00:00:00 2001 From: dave-365 <53798424+dave-365@users.noreply.github.com> Date: Sat, 14 Nov 2026 16:51:26 +0530 Subject: [PATCH 4/4] Update napi.h __cpp_concepts should be checked after #include https://en.cppreference.com/cpp/header/version: Co-authored-by: Chengzhong Wu --- napi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/napi.h b/napi.h index 8b5bb218f..412dfd584 100644 --- a/napi.h +++ b/napi.h @@ -24,14 +24,14 @@ #include #include -#if (defined(__cpp_concepts) && __cpp_concepts >= 201907L) || \ - (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) #if defined(__has_include) -#if __has_include() -#include -#define NAPI_HAS_CPP20_CONCEPTS 1 +#if __has_include() +#include #endif #endif +#if defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 202002L +#include +#define NAPI_HAS_CPP20_CONCEPTS 1 #endif // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known