diff --git a/napi-inl.h b/napi-inl.h index 59f1ca14e..a1df4a483 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -604,10 +604,12 @@ bool Maybe::operator==(const Maybe& other) const { (!IsJust() || Unwrap() == other.Unwrap()); } +#if !defined(NAPI_HAS_CPP20_COMPARISONS) template bool Maybe::operator!=(const Maybe& other) const { return !operator==(other); } +#endif template Maybe::Maybe() : _has_value(false) {} @@ -790,9 +792,11 @@ inline bool Value::operator==(const Value& other) const { return StrictEquals(other); } +#if !defined(NAPI_HAS_CPP20_COMPARISONS) inline bool Value::operator!=(const Value& other) const { return !this->operator==(other); } +#endif inline bool Value::StrictEquals(const Value& other) const { bool result; @@ -1961,10 +1965,12 @@ inline bool Object::const_iterator::operator==( return _index == other._index; } +#if !defined(NAPI_HAS_CPP20_COMPARISONS) inline bool Object::const_iterator::operator!=( const const_iterator& other) const { return _index != other._index; } +#endif inline const std::pair> Object::const_iterator::operator*() const { @@ -1998,9 +2004,11 @@ inline bool Object::iterator::operator==(const iterator& other) const { return _index == other._index; } +#if !defined(NAPI_HAS_CPP20_COMPARISONS) inline bool Object::iterator::operator!=(const iterator& other) const { return _index != other._index; } +#endif inline std::pair> Object::iterator::operator*() { @@ -3746,10 +3754,12 @@ inline bool Reference::operator==(const Reference& other) const { return this->Value().StrictEquals(other.Value()); } +#if !defined(NAPI_HAS_CPP20_COMPARISONS) template inline bool Reference::operator!=(const Reference& other) const { return !this->operator==(other); } +#endif template inline Napi::Env Reference::Env() const { diff --git a/napi.h b/napi.h index 6b4692c7d..79dd115d9 100644 --- a/napi.h +++ b/napi.h @@ -24,6 +24,17 @@ #include #include +#if (defined(__cpp_impl_three_way_comparison) && \ + __cpp_impl_three_way_comparison >= 201907L) || \ + (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#if defined(__has_include) +#if __has_include() +#include +#define NAPI_HAS_CPP20_COMPARISONS 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 @@ -292,7 +303,18 @@ class Maybe { bool UnwrapTo(T* out) const; bool operator==(const Maybe& other) const; +#if !defined(NAPI_HAS_CPP20_COMPARISONS) bool operator!=(const Maybe& other) const; +#else + template + constexpr auto operator<=>(const Maybe& other) const + -> std::compare_three_way_result_t { + if (_has_value && other._has_value) { + return _value <=> other._value; + } + return _has_value <=> other._has_value; + } +#endif private: Maybe(); @@ -361,9 +383,14 @@ class BasicEnv { // 'Napi::Env' and 'Napi::Env') // // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr +#if defined(NAPI_HAS_CPP20_COMPARISONS) + bool operator==(const BasicEnv& other) const = default; + auto operator<=>(const BasicEnv& other) const = default; +#else bool operator==(const BasicEnv& other) const { return _env == other._env; } +#endif bool operator==(std::nullptr_t /*other*/) const { return _env == nullptr; } @@ -505,8 +532,10 @@ class Value { /// Tests if this value strictly equals another value. bool operator==(const Value& other) const; +#if !defined(NAPI_HAS_CPP20_COMPARISONS) /// Tests if this value does not strictly equal another value. bool operator!=(const Value& other) const; +#endif /// Tests if this value strictly equals another value. bool StrictEquals(const Value& other) const; @@ -1258,7 +1287,9 @@ class Object::const_iterator { inline bool operator==(const const_iterator& other) const; +#if !defined(NAPI_HAS_CPP20_COMPARISONS) inline bool operator!=(const const_iterator& other) const; +#endif inline const std::pair> operator*() const; @@ -1282,7 +1313,9 @@ class Object::iterator { inline bool operator==(const iterator& other) const; +#if !defined(NAPI_HAS_CPP20_COMPARISONS) inline bool operator!=(const iterator& other) const; +#endif inline std::pair> operator*(); @@ -1834,7 +1867,9 @@ class Reference { operator napi_ref() const; bool operator==(const Reference& other) const; +#if !defined(NAPI_HAS_CPP20_COMPARISONS) bool operator!=(const Reference& other) const; +#endif Napi::Env Env() const; bool IsEmpty() const; diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index 7ec3b7e04..6f4b7939f 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -36,6 +36,41 @@ static Value ValueReturnsCorrectEnv(const CallbackInfo& info) { return Boolean::New(info.Env(), testValue.Env() == info.Env()); } +static Value TestEnvComparisons(const CallbackInfo& info) { + Env env = info.Env(); + Env env2 = info.Env(); + bool result = (env == env2) && !(env != env2) && (env != nullptr) && + !(env == nullptr) && (nullptr != env) && !(nullptr == env); +#if defined(NAPI_HAS_CPP20_COMPARISONS) + result = result && ((env <=> env2) == 0); +#endif + return Boolean::New(info.Env(), result); +} + +static Value TestMaybeComparisons(const CallbackInfo& info) { + Maybe m_none1 = Nothing(); + Maybe m_none2 = Nothing(); + Maybe m_one = Just(1); + Maybe m_two = Just(2); + + bool ok = (m_none1 == m_none2) && (m_none1 != m_one) && (m_one == m_one) && + (m_one != m_two); +#if defined(NAPI_HAS_CPP20_COMPARISONS) + ok = ok && (m_none1 < m_one) && (m_one > m_none1) && (m_one < m_two) && + (m_two > m_one) && (m_one <= m_two) && (m_two >= m_one) && + (m_one <= m_one) && (m_one >= m_one) && ((m_none1 <=> m_none2) == 0) && + ((m_none1 <=> m_one) < 0) && ((m_one <=> m_none1) > 0) && + ((m_one <=> m_two) < 0) && ((m_two <=> m_one) > 0); + + Maybe md_none = Nothing(); + Maybe md_one = Just(1.5); + Maybe md_two = Just(2.5); + ok = ok && (md_none < md_one) && (md_one < md_two) && + ((md_one <=> md_two) < 0); +#endif + return Boolean::New(info.Env(), ok); +} + static Value EmptyValueReturnNullPtrOnCast(const CallbackInfo& info) { Value emptyValue; bool isNullPtr = static_cast(emptyValue) == nullptr; @@ -164,6 +199,8 @@ Object InitBasicTypesValue(Env env) { exports["assertValueReturnsCorrectEnv"] = Function::New(env, ValueReturnsCorrectEnv); + exports["assertEnvComparisons"] = Function::New(env, TestEnvComparisons); + exports["assertMaybeComparisons"] = Function::New(env, TestMaybeComparisons); exports["assertEmptyValReturnNullPtrOnCast"] = Function::New(env, EmptyValueReturnNullPtrOnCast); diff --git a/test/basic_types/value.js b/test/basic_types/value.js index adf73d39d..35c5f2a1c 100644 --- a/test/basic_types/value.js +++ b/test/basic_types/value.js @@ -137,6 +137,8 @@ function test (binding) { assertValueStrictlyEqual(value); assertValueStrictlyNonEqual(value); assertValueReturnsCorrectEnv(value); + assert(value.assertEnvComparisons()); + assert(value.assertMaybeComparisons()); assertEmptyValueNullPtrOnCast(value); assertNonEmptyReturnValOnCast(value); diff --git a/test/binding.gyp b/test/binding.gyp index 9ff334b64..381d5e153 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -118,6 +118,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/maybe/check.cc b/test/maybe/check.cc index 74acf7e38..68f1aeeef 100644 --- a/test/maybe/check.cc +++ b/test/maybe/check.cc @@ -30,6 +30,28 @@ void TestMaybeOperatorOverload(const CallbackInfo& info) { assert(fn_a.Call({}) == fn_a.Call({})); assert(fn_a.Call({}) != fn_b.Call({})); +#if defined(NAPI_HAS_CPP20_COMPARISONS) + Maybe m_none1 = Nothing(); + Maybe m_none2 = Nothing(); + Maybe m_one = Just(1); + Maybe m_two = Just(2); + + assert(m_none1 == m_none2); + assert(m_none1 != m_one); + assert(m_none1 < m_one); + assert(m_one > m_none1); + assert(m_one < m_two); + assert(m_two > m_one); + assert(m_one <= m_two); + assert(m_two >= m_one); + assert(m_one <= m_one); + assert(m_one >= m_one); + assert((m_none1 <=> m_none2) == 0); + assert((m_none1 <=> m_one) < 0); + assert((m_one <=> m_none1) > 0); + assert((m_one <=> m_two) < 0); + assert((m_two <=> m_one) > 0); +#endif } void NormalJsCallback(const CallbackInfo& info) {