From 23bffc6366f5ff7ba4a9d8185878bb3df87e4aab Mon Sep 17 00:00:00 2001 From: MangelSpec Date: Mon, 4 Apr 2022 18:45:42 +0200 Subject: [PATCH 1/5] added std::optional handling with v8::Undefined if empty --- v8pp/convert.hpp | 38 +++++++++++++++++++++++++++++++++++++- v8pp/utility.hpp | 15 +++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index db04168..669677d 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -288,6 +288,41 @@ struct convert::value>::typ } }; +// convert std::optional <-> value or undefined +template +struct convert> +{ + using from_type = std::optional; + using to_type = typename convert::to_type; // v8::Local; + + static bool is_valid(v8::Isolate* isolate, v8::Local value) + { + return convert::is_valid(isolate, value); + } + + static from_type from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw invalid_argument(isolate, value, "Optional"); + } + + return convert::from_v8(isolate, value); + } + + static to_type to_v8(v8::Isolate* isolate, from_type const& value) + { + if (value.has_value()) + { + return convert::to_v8(isolate, value.value()); + } + else + { + return to_type::Cast(v8::Undefined(isolate)); + } + } +}; + // convert std::tuple <-> Array template struct convert> @@ -658,7 +693,8 @@ struct is_wrapped_class : std::conjunction< std::negation>, std::negation>, std::negation>, - std::negation>> + std::negation>, + std::negation>> { }; diff --git a/v8pp/utility.hpp b/v8pp/utility.hpp index 1fcee57..b2351a9 100644 --- a/v8pp/utility.hpp +++ b/v8pp/utility.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace v8pp::detail { @@ -162,6 +163,20 @@ struct is_shared_ptr> : std::true_type { }; +///////////////////////////////////////////////////////////////////////////// +// +// is_optional +// +template +struct is_optional : std::false_type +{ +}; + +template +struct is_optional> : std::true_type +{ +}; + ///////////////////////////////////////////////////////////////////////////// // // Function traits From 212bdeeccbb2688dd3501caa90533b4c8072d45f Mon Sep 17 00:00:00 2001 From: MangelSpec Date: Fri, 8 Apr 2022 17:59:52 +0200 Subject: [PATCH 2/5] add optional args support to call_from_v8 improve convert --- v8pp/call_from_v8.hpp | 19 ++++++++++++++++++- v8pp/convert.hpp | 13 +++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/v8pp/call_from_v8.hpp b/v8pp/call_from_v8.hpp index 771bbc9..c52464e 100644 --- a/v8pp/call_from_v8.hpp +++ b/v8pp/call_from_v8.hpp @@ -10,12 +10,29 @@ namespace v8pp::detail { +template +struct optional_count_impl; + +template +struct optional_count_impl> +{ + constexpr static std::size_t count = + (0 + ... + (is_optional::value ? 1 : 0)); +}; + +template +constexpr auto optional_count() -> std::size_t +{ + return optional_count_impl::count; +} + template struct call_from_v8_traits { static constexpr size_t offset = Offset; static constexpr bool is_mem_fun = std::is_member_function_pointer_v; using arguments = typename function_traits::arguments; + static constexpr size_t optional_arg_count = optional_count(); static constexpr size_t arg_count = std::tuple_size_v - is_mem_fun - offset; @@ -89,7 +106,7 @@ decltype(auto) call_from_v8(F&& func, v8::FunctionCallbackInfo const& using call_traits = call_from_v8_traits; using indices = std::make_index_sequence; - if (args.Length() != call_traits::arg_count) + if (args.Length() > call_traits::arg_count || args.Length() < call_traits::arg_count - call_traits::optional_arg_count) { throw std::runtime_error( "Argument count does not match function definition. Expected " + diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index 669677d..79e3009 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -293,7 +293,7 @@ template struct convert> { using from_type = std::optional; - using to_type = typename convert::to_type; // v8::Local; + using to_type = typename convert::to_type; static bool is_valid(v8::Isolate* isolate, v8::Local value) { @@ -304,7 +304,7 @@ struct convert> { if (!is_valid(isolate, value)) { - throw invalid_argument(isolate, value, "Optional"); + return from_type{}; } return convert::from_v8(isolate, value); @@ -916,6 +916,15 @@ v8::Local to_v8(v8::Isolate* isolate, } #endif +template +auto to_v8(v8::Isolate* isolate, std::optional const& value) -> v8::Local +{ + if (value.has_value()) + return convert::to_v8(isolate, value.value()); + else + return v8::Undefined(isolate); +} + template auto to_v8(v8::Isolate* isolate, T const& value) { From 85dc964bef94764a6ac28f11cb0659985cea35eb Mon Sep 17 00:00:00 2001 From: mangelspec Date: Thu, 29 Dec 2022 17:15:21 +0100 Subject: [PATCH 3/5] add monostate support to variant for undefined types --- v8pp/convert.hpp | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index 79e3009..483ff6d 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -148,6 +148,33 @@ struct convert : convert> #endif // converter specializations for primitive types +template<> +struct convert +{ + using from_type = std::monostate; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return value.IsEmpty() || value->IsNullOrUndefined(); + } + + static from_type from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw invalid_argument(isolate, value, "Undefined"); + } + + return from_type{}; + } + + static to_type to_v8(v8::Isolate* isolate, from_type value) + { + return v8::Undefined(isolate); + } +}; + template<> struct convert { @@ -431,6 +458,10 @@ struct convert> return alternate(isolate, value); } } + else if (value->IsNullOrUndefined()) + { + return alternate(isolate, value); + } else { return alternate(isolate, value); @@ -450,6 +481,9 @@ struct convert> template using is_bool = std::is_same; + template + using is_monostate = std::is_same; + template using is_integral_not_bool = std::bool_constant::value && !is_bool::value>; @@ -485,7 +519,14 @@ struct convert> template static bool try_as(v8::Isolate* isolate, v8::Local value, std::optional& result) { - if constexpr (detail::is_shared_ptr::value) + if constexpr (std::is_same::value) + { + if(v8pp::convert::is_valid(isolate, value)) + { + result = std::monostate{}; + } + } + else if constexpr (detail::is_shared_ptr::value) { using U = typename T::element_type; if (auto obj = v8pp::class_::unwrap_object(isolate, value)) From b36c15573cd1e6d304a04c9deb8816b2c6d65a82 Mon Sep 17 00:00:00 2001 From: Pavel Medvedev Date: Wed, 19 Nov 2025 23:05:09 +0100 Subject: [PATCH 4/5] simplify `optional_count` --- v8pp/call_from_v8.hpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/v8pp/call_from_v8.hpp b/v8pp/call_from_v8.hpp index c52464e..939d174 100644 --- a/v8pp/call_from_v8.hpp +++ b/v8pp/call_from_v8.hpp @@ -11,28 +11,21 @@ namespace v8pp::detail { template -struct optional_count_impl; +struct optional_count; template -struct optional_count_impl> +struct optional_count> { - constexpr static std::size_t count = - (0 + ... + (is_optional::value ? 1 : 0)); + static constexpr size_t value = (0 + ... + is_optional::value); }; -template -constexpr auto optional_count() -> std::size_t -{ - return optional_count_impl::count; -} - template struct call_from_v8_traits { static constexpr size_t offset = Offset; static constexpr bool is_mem_fun = std::is_member_function_pointer_v; using arguments = typename function_traits::arguments; - static constexpr size_t optional_arg_count = optional_count(); + static constexpr size_t optional_arg_count = optional_count::value; static constexpr size_t arg_count = std::tuple_size_v - is_mem_fun - offset; @@ -106,7 +99,8 @@ decltype(auto) call_from_v8(F&& func, v8::FunctionCallbackInfo const& using call_traits = call_from_v8_traits; using indices = std::make_index_sequence; - if (args.Length() > call_traits::arg_count || args.Length() < call_traits::arg_count - call_traits::optional_arg_count) + size_t const arg_count = args.Length(); + if (arg_count > call_traits::arg_count || arg_count < call_traits::arg_count - call_traits::optional_arg_count) { throw std::runtime_error( "Argument count does not match function definition. Expected " + From 31d1331d9ad9ff3a446306a9b0a9009773bb8dee Mon Sep 17 00:00:00 2001 From: Pavel Medvedev Date: Wed, 19 Nov 2025 23:08:07 +0100 Subject: [PATCH 5/5] fix `std::optional` in fields of tuple/variant --- test/test.hpp | 6 +++ test/test_convert.cpp | 110 +++++++++++++++++++++++++++++++++++++++--- v8pp/convert.hpp | 109 ++++++++++++++++++++++++++++++----------- 3 files changed, 189 insertions(+), 36 deletions(-) diff --git a/test/test.hpp b/test/test.hpp index b3c99fa..0f69ac0 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -157,6 +157,12 @@ std::ostream& operator<<(std::ostream& os, Enum value) return os << static_cast>(value); } +template +std::ostream& operator<<(std::ostream& os, std::optional const& optional) +{ + return optional ? os << *optional : os << "nullopt"; +} + template std::ostream& operator<<(std::ostream& os, std::tuple const& tuple) { diff --git a/test/test_convert.cpp b/test/test_convert.cpp index ca0b80e..b2affb6 100644 --- a/test/test_convert.cpp +++ b/test/test_convert.cpp @@ -61,25 +61,88 @@ void test_string_conv(v8::Isolate* isolate, Char const (&str)[N]) v8pp::from_v8(isolate, v8pp::to_v8(isolate, empty, 0)), empty); } +struct address +{ + std::string zip; + std::string city; + std::string street; + std::string house; + std::optional flat; + + //for test framework + bool operator==(address const& other) const = default; + + friend std::ostream& operator<<(std::ostream& os, address const& a) + { + return os << "address: " << a.zip << " " << a.city << " " << a.street << " " << a.house << " " << a.flat; + } +}; + struct person { std::string name; int age; + std::optional
home; //for test framework - bool operator!=(person const& other) const - { - return name != other.name || age != other.age; - } + bool operator==(person const& other) const = default; friend std::ostream& operator<<(std::ostream& os, person const& p) { - return os << "person: " << p.name << " age: " << p.age; + return os << "person: " << p.name << " age: " << p.age << " home: " << p.home; } }; namespace v8pp { +template<> +struct convert
+{ + using from_type = address; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return !value.IsEmpty() && value->IsObject(); + } + + static to_type to_v8(v8::Isolate* isolate, address const& a) + { + v8::EscapableHandleScope scope(isolate); + v8::Local obj = v8::Object::New(isolate); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "zip"), v8pp::to_v8(isolate, a.zip)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "city"), v8pp::to_v8(isolate, a.city)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "street"), v8pp::to_v8(isolate, a.street)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "house"), v8pp::to_v8(isolate, a.house)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "flat"), v8pp::to_v8(isolate, a.flat)).FromJust(); + return scope.Escape(obj); + } + + static from_type from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw std::runtime_error("expected object"); + } + + v8::HandleScope scope(isolate); + v8::Local obj = value.As(); + + address result; + result.zip = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "zip")).ToLocalChecked()); + result.city = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "city")).ToLocalChecked()); + result.street = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "street")).ToLocalChecked()); + result.house = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "house")).ToLocalChecked()); + result.flat = v8pp::from_v8(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "flat")).ToLocalChecked()); + return result; + } +}; + template<> struct convert { @@ -97,9 +160,11 @@ struct convert v8::Local obj = v8::Object::New(isolate); obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "name"), v8pp::to_v8(isolate, p.name)).FromJust(); obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "age"), v8pp::to_v8(isolate, p.age)).FromJust(); + obj->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "home"), v8pp::to_v8(isolate, p.home)).FromJust(); /* Simpler after #include set_option(isolate, obj, "name", p.name); set_option(isolate, obj, "age", p.age); + set_option(isolate, obj, "home", p.home); */ return scope.Escape(obj); } @@ -119,10 +184,13 @@ struct convert obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "name")).ToLocalChecked()); result.age = v8pp::from_v8(isolate, obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "age")).ToLocalChecked()); + result.home = v8pp::from_v8>(isolate, + obj->Get(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "home")).ToLocalChecked()); /* Simpler after #include get_option(isolate, obj, "name", result.name); get_option(isolate, obj, "age", result.age); + get_option(isolate, obj, "home", result.home); */ return result; } @@ -135,6 +203,25 @@ void test_convert_user_type(v8::Isolate* isolate) person p; p.name = "Al"; p.age = 33; test_conv(isolate, p); + p.home = { .zip = "90210", .city = "Beverly Hills", .street = "Main St", .house = "123", .flat = "B2" }; + test_conv(isolate, p); +} + +void test_convert_optional(v8::Isolate* isolate) +{ + test_conv(isolate, std::optional{42}); + test_conv(isolate, std::optional{std::nullopt}); + + check("null", v8pp::from_v8>(isolate, v8::Null(isolate)) == std::nullopt); + check("undefined", v8pp::from_v8>(isolate, v8::Undefined(isolate)) == std::nullopt); + + check("nullopt", v8pp::to_v8(isolate, std::nullopt)->IsNull()); + check("monostate", v8pp::to_v8(isolate, std::monostate{})->IsUndefined()); + + check_ex("wrong optional type", [isolate]() + { + v8pp::from_v8>(isolate, v8pp::to_v8(isolate, std::optional{"aa"})); + }); } void test_convert_tuple(v8::Isolate* isolate) @@ -148,6 +235,9 @@ void test_convert_tuple(v8::Isolate* isolate) std::tuple const tuple_3{ 1, 2, 3 }; test_conv(isolate, tuple_3); + std::tuple, int, std::optional> const tuple_4{ 1, 2, 3, std::nullopt }; + test_conv(isolate, tuple_4); + check_ex("Tuple", [isolate, &tuple_1]() { // incorrect number of elements @@ -316,8 +406,9 @@ void test_convert_variant(v8::Isolate* isolate) check_arithmetic_reversed(2, 5.5f, true); check_arithmetic_reversed(-2, 2.2f, false); - variant_check, float, std::string> check_vector{ isolate }; - check_vector({1.f, 2.f, 3.f}, 4.f, "testing"); + variant_check, float, std::optional> check_vector{ isolate }; + check_vector({1.f, 2.f, 3.f}, 4.f, std::optional("testing")); + check_vector(std::vector{}, 0.f, std::optional{}); // The order here matters variant_check order_check{ isolate }; @@ -353,6 +444,10 @@ void test_convert_variant(v8::Isolate* isolate) variant_check> unordered_multimap_check{ isolate }; unordered_multimap_check(U2{3.0}, std::unordered_multimap{ { 'a', U{1} }, { 'b', U{2} } }); + + variant_check, bool> optional_check{ isolate }; + optional_check(true, "test", 1); + optional_check(0, std::optional{}, false); } void test_convert() @@ -405,6 +500,7 @@ void test_convert() v8pp::from_v8>(isolate, v8pp::to_v8(isolate, list.begin(), list.end())), vector); test_convert_user_type(isolate); + test_convert_optional(isolate); test_convert_tuple(isolate); test_convert_variant(isolate); } diff --git a/v8pp/convert.hpp b/v8pp/convert.hpp index 483ff6d..7c25ff9 100644 --- a/v8pp/convert.hpp +++ b/v8pp/convert.hpp @@ -156,25 +156,52 @@ struct convert static bool is_valid(v8::Isolate*, v8::Local value) { - return value.IsEmpty() || value->IsNullOrUndefined(); + return value.IsEmpty() || value->IsUndefined(); } - static from_type from_v8(v8::Isolate* isolate, v8::Local value) + static std::monostate from_v8(v8::Isolate* isolate, v8::Local value) { if (!is_valid(isolate, value)) { throw invalid_argument(isolate, value, "Undefined"); } - return from_type{}; + return std::monostate{}; } - static to_type to_v8(v8::Isolate* isolate, from_type value) + static v8::Local to_v8(v8::Isolate* isolate, std::monostate) { return v8::Undefined(isolate); } }; +template<> +struct convert +{ + using from_type = std::nullopt_t; + using to_type = v8::Local; + + static bool is_valid(v8::Isolate*, v8::Local value) + { + return value.IsEmpty() || value->IsNull(); + } + + static std::nullopt_t from_v8(v8::Isolate* isolate, v8::Local value) + { + if (!is_valid(isolate, value)) + { + throw invalid_argument(isolate, value, "Null"); + } + + return std::nullopt; + } + + static v8::Local to_v8(v8::Isolate* isolate, std::nullopt_t) + { + return v8::Null(isolate); + } +}; + template<> struct convert { @@ -320,32 +347,38 @@ template struct convert> { using from_type = std::optional; - using to_type = typename convert::to_type; + using to_type = v8::Local; static bool is_valid(v8::Isolate* isolate, v8::Local value) { - return convert::is_valid(isolate, value); + return value.IsEmpty() || value->IsNullOrUndefined() || convert::is_valid(isolate, value); } static from_type from_v8(v8::Isolate* isolate, v8::Local value) { - if (!is_valid(isolate, value)) + if (value.IsEmpty() || value->IsNullOrUndefined()) { - return from_type{}; + return std::nullopt; + } + else if (convert::is_valid(isolate, value)) + { + return convert::from_v8(isolate, value); + } + else + { + throw invalid_argument(isolate, value, "Optional"); } - - return convert::from_v8(isolate, value); } - static to_type to_v8(v8::Isolate* isolate, from_type const& value) + static to_type to_v8(v8::Isolate* isolate, std::optional const& value) { - if (value.has_value()) + if (value) { - return convert::to_v8(isolate, value.value()); + return convert::to_v8(isolate, *value); } else { - return to_type::Cast(v8::Undefined(isolate)); + return v8::Undefined(isolate); } } }; @@ -426,42 +459,46 @@ struct convert> v8::HandleScope scope(isolate); + if (value.IsEmpty() || value->IsNull()) + { + return alternate(isolate, value); + } + else if (value->IsUndefined()) + { + return alternate(isolate, value); + } if (value->IsBoolean()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsInt32() || value->IsUint32()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsNumber()) { //TODO: 64-bit integers - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsString()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsArray()) { - return alternate(isolate, value); + return alternate(isolate, value); } else if (value->IsObject()) { if (is_map_object(isolate, value.As())) { - return alternate(isolate, value); + return alternate(isolate, value); } else { - return alternate(isolate, value); + return alternate(isolate, value); } } - else if (value->IsNullOrUndefined()) - { - return alternate(isolate, value); - } else { return alternate(isolate, value); @@ -484,6 +521,9 @@ struct convert> template using is_monostate = std::is_same; + template + using is_nullopt = std::is_same; + template using is_integral_not_bool = std::bool_constant::value && !is_bool::value>; @@ -521,11 +561,18 @@ struct convert> { if constexpr (std::is_same::value) { - if(v8pp::convert::is_valid(isolate, value)) + if (v8pp::convert::is_valid(isolate, value)) { result = std::monostate{}; } } + else if constexpr (std::is_same::value) + { + if (v8pp::convert::is_valid(isolate, value)) + { + result = std::nullopt; + } + } else if constexpr (detail::is_shared_ptr::value) { using U = typename T::element_type; @@ -958,12 +1005,16 @@ v8::Local to_v8(v8::Isolate* isolate, #endif template -auto to_v8(v8::Isolate* isolate, std::optional const& value) -> v8::Local +v8::Local to_v8(v8::Isolate* isolate, std::optional const& value) { - if (value.has_value()) - return convert::to_v8(isolate, value.value()); + if (value) + { + return convert::to_v8(isolate, *value); + } else + { return v8::Undefined(isolate); + } } template