From 4d8e5dafb92049c329f075bb7bc154ab11355c44 Mon Sep 17 00:00:00 2001 From: Filip Sajdak Date: Tue, 27 Aug 2024 23:34:18 +0200 Subject: [PATCH 1/5] is()/as(): Refactor is() and as() for std::any --- include/cpp2util.h | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index 1ee0a06f76..52fa433128 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -2083,28 +2083,22 @@ auto as(X&& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS) -> decltype(auto) // is Type // -template - requires (std::is_same_v && !std::is_same_v && !std::is_same_v) -constexpr auto is( X const& x ) -> bool - { return x.type() == Typeid(); } - -template - requires (std::is_same_v && std::is_same_v) -constexpr auto is( X const& x ) -> bool - { return !x.has_value(); } - +template X> +constexpr auto is( X const& x ) -> bool{ + if (!x.has_value()) { + return std::is_same_v; + } + return x.type() == Typeid(); +} // is Value // inline constexpr auto is( std::any const& x, auto&& value ) -> bool { // Predicate case - if constexpr (requires{ bool{ value(x) }; }) { + if constexpr (valid_predicate) { return value(x); } - else if constexpr (std::is_function_v || requires{ &value.operator(); }) { - return false; - } // Value case else if constexpr (requires{ bool{ *std::any_cast(&x) == value }; }) { @@ -2118,10 +2112,12 @@ inline constexpr auto is( std::any const& x, auto&& value ) -> bool // as // -template - requires (!std::is_reference_v && std::is_same_v && !std::is_same_v) -constexpr auto as( X const& x ) -> T - { return std::any_cast( x ); } +template X> +constexpr auto as( X && x ) -> decltype(auto) { + constness_like_t* ptr = std::any_cast( &x ); + if (!ptr) { Throw( std::bad_any_cast(), "'as' cast failed for 'std::any'"); } + return cpp2::forward_like(*ptr); +} //------------------------------------------------------------------------------------------------------------- From 0d3917b63084382bfe4632baaa8e2aa2d71eb6c0 Mon Sep 17 00:00:00 2001 From: Filip Sajdak Date: Tue, 27 Aug 2024 23:34:18 +0200 Subject: [PATCH 2/5] is()/as(): refactor is() and as() for std::optional --- include/cpp2util.h | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index 52fa433128..41bbb9beb9 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -2126,16 +2126,16 @@ constexpr auto as( X && x ) -> decltype(auto) { // is Type // -template - requires std::is_same_v> -constexpr auto is( X const& x ) -> bool - { return x.has_value(); } - -template - requires std::is_same_v -constexpr auto is( std::optional const& x ) -> bool - { return !x.has_value(); } - +template X> +constexpr auto is( X const& x ) -> bool { + if (!x.has_value()) { + return std::same_as; + } + if constexpr (requires { static_cast(*x);}) { + return true; + } + return false; +} // is Value // @@ -2143,12 +2143,9 @@ template constexpr auto is( std::optional const& x, auto&& value ) -> bool { // Predicate case - if constexpr (requires{ bool{ value(x) }; }) { + if constexpr (valid_predicate) { return value(x); } - else if constexpr (std::is_function_v || requires{ &value.operator(); }) { - return false; - } // Value case else if constexpr (requires{ bool{ x.value() == value }; }) { @@ -2160,10 +2157,17 @@ constexpr auto is( std::optional const& x, auto&& value ) -> bool // as // -template - requires std::is_same_v> -constexpr auto as( X const& x ) -> decltype(auto) - { return x.value(); } +template X> +constexpr auto as( X&& x ) -> decltype(auto) { + constness_like_t* ptr = nullptr; + if constexpr (requires { static_cast&>(*x); }) { + if (x.has_value()) { + ptr = &static_cast&>(*x); + } + } + if (!ptr) { Throw( std::bad_optional_access(), "'as' cast failed for 'std::optional'"); } + return cpp2::forward_like(*ptr); +} } // impl From fb5e13b97a71d7715821165daf3b2b674fb7f99e Mon Sep 17 00:00:00 2001 From: Filip Sajdak Date: Wed, 28 Aug 2024 00:04:00 +0200 Subject: [PATCH 3/5] Fix tests --- .../pure2-default-arguments.cpp.output | 32 +++++++++---------- ...mixed-bugfix-for-ufcs-non-local.cpp.output | 20 ++++++------ ...mixed-bugfix-for-ufcs-non-local.cpp.output | 20 ++++++------ 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output b/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output index 7ed50a83d1..b6358b20cd 100644 --- a/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output +++ b/regression-tests/test-results/gcc-10-c++20/pure2-default-arguments.cpp.output @@ -1,66 +1,66 @@ In file included from pure2-default-arguments.cpp:7: ../../../include/cpp2util.h:2086:28: error: local variable ‘obj’ may not appear in this context - 2086 | template + 2086 | template X> | ^~~ ../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ 2047 | return false; | ^ ../../../include/cpp2util.h:2086:15: note: in expansion of macro ‘CPP2_FORWARD’ - 2086 | template + 2086 | template X> | ^~~~~~~~~~~~ ../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ 2107 | } | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ ../../../include/cpp2util.h:2086:92: error: local variable ‘params’ may not appear in this context - 2086 | template + 2086 | template X> | ^ ../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ 2047 | return false; | ^ ../../../include/cpp2util.h:2086:79: note: in expansion of macro ‘CPP2_FORWARD’ - 2086 | template + 2086 | template X> | ^ ../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ 2107 | } | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ ../../../include/cpp2util.h:2087:74: error: local variable ‘obj’ may not appear in this context - 2087 | requires (std::is_same_v && !std::is_same_v && !std::is_same_v) - | ^~~ + 2087 | constexpr auto is( X const& x ) -> bool{ + | ^ ../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ 2047 | return false; | ^ ../../../include/cpp2util.h:2087:61: note: in expansion of macro ‘CPP2_FORWARD’ - 2087 | requires (std::is_same_v && !std::is_same_v && !std::is_same_v) - | ^~~~~~~~~~~~ + 2087 | constexpr auto is( X const& x ) -> bool{ + | ^ ../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ 2107 | } | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ ../../../include/cpp2util.h:2087:93: error: local variable ‘params’ may not appear in this context - 2087 | requires (std::is_same_v && !std::is_same_v && !std::is_same_v) - | ^~~~~~ + 2087 | constexpr auto is( X const& x ) -> bool{ + | ^ ../../../include/cpp2util.h:2047:34: note: in definition of macro ‘CPP2_UFCS_IDENTITY’ 2047 | return false; | ^ ../../../include/cpp2util.h:2087:80: note: in expansion of macro ‘CPP2_FORWARD’ - 2087 | requires (std::is_same_v && !std::is_same_v && !std::is_same_v) - | ^~~~~~~~~~~~ + 2087 | constexpr auto is( X const& x ) -> bool{ + | ^ ../../../include/cpp2util.h:2107:22: note: in expansion of macro ‘CPP2_UFCS_CONSTRAINT_ARG’ 2107 | } | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ pure2-default-arguments.cpp2:6:22: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ pure2-default-arguments.cpp2:6:61: error: ‘std::source_location’ has not been declared diff --git a/regression-tests/test-results/gcc-13-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output b/regression-tests/test-results/gcc-13-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output index b08a69eb07..20b01c3432 100644 --- a/regression-tests/test-results/gcc-13-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output +++ b/regression-tests/test-results/gcc-13-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output @@ -1,41 +1,41 @@ In file included from mixed-bugfix-for-ufcs-non-local.cpp:6: ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:13:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:13:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:31:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:31:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:33:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:33:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid diff --git a/regression-tests/test-results/gcc-14-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output b/regression-tests/test-results/gcc-14-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output index b08a69eb07..20b01c3432 100644 --- a/regression-tests/test-results/gcc-14-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output +++ b/regression-tests/test-results/gcc-14-c++2b/mixed-bugfix-for-ufcs-non-local.cpp.output @@ -1,41 +1,41 @@ In file included from mixed-bugfix-for-ufcs-non-local.cpp:6: ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:13:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:13:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:31:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:31:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:33:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:33:36: error: template argument 1 is invalid ../../../include/cpp2util.h:2100:1: error: lambda-expression in template parameter type - 2100 | { + 2100 | return value(x); | ^ ../../../include/cpp2util.h:2137:59: note: in expansion of macro ‘CPP2_UFCS_’ - 2137 | + 2137 | return false; | ^ mixed-bugfix-for-ufcs-non-local.cpp2:21:12: note: in expansion of macro ‘CPP2_UFCS_NONLOCAL’ mixed-bugfix-for-ufcs-non-local.cpp2:21:36: error: template argument 1 is invalid From 016f9edd5f35665f996e0a57d141b5407869c442 Mon Sep 17 00:00:00 2001 From: Filip Sajdak Date: Sat, 31 Aug 2024 00:28:42 +0200 Subject: [PATCH 4/5] as(): make as() cast to use type_safety.enforce The current implementation for std::any, std::optional, and std::variant throws exceptions. This behaviour is inconsistent to runtime checked as() for signed/unsigned integral cast. This implementation allows to setup one approch for type_safety issues. Current behaviour is to terminate but it can be changed to e.g. throwing exception. --- include/cpp2util.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/cpp2util.h b/include/cpp2util.h index 41bbb9beb9..5fb96e34a3 100644 --- a/include/cpp2util.h +++ b/include/cpp2util.h @@ -908,6 +908,7 @@ class contract_group { constexpr contract_group (handler h = {}) : reporter{h} { } constexpr auto set_handler(handler h = {}) { reporter = h; } + constexpr auto get_handler() const -> handler { return reporter; } constexpr auto is_active () const -> bool { return reporter != handler{}; } constexpr auto enforce(bool b, CPP2_MESSAGE_PARAM msg = "" CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) @@ -2073,7 +2074,7 @@ auto as(X&& x CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT_AS) -> decltype(auto) if constexpr (std::is_same_v< typename It::type, C >) { if (CPP2_FORWARD(x).index() == It::index) { ptr = &std::get(x); return true; } }; return false; }); - if (!ptr) { Throw( std::bad_variant_access(), "'as' cast failed for 'variant'"); } + type_safety.enforce(ptr, "'as' cast failed for 'variant'"); return cpp2::forward_like(*ptr); } @@ -2115,7 +2116,7 @@ inline constexpr auto is( std::any const& x, auto&& value ) -> bool template X> constexpr auto as( X && x ) -> decltype(auto) { constness_like_t* ptr = std::any_cast( &x ); - if (!ptr) { Throw( std::bad_any_cast(), "'as' cast failed for 'std::any'"); } + type_safety.enforce(ptr, "'as' cast failed for 'std::any'"); return cpp2::forward_like(*ptr); } @@ -2165,7 +2166,7 @@ constexpr auto as( X&& x ) -> decltype(auto) { ptr = &static_cast&>(*x); } } - if (!ptr) { Throw( std::bad_optional_access(), "'as' cast failed for 'std::optional'"); } + type_safety.enforce(ptr, "'as' cast failed for 'std::optional'"); return cpp2::forward_like(*ptr); } From 0b6e6a151ad5d45082e0c88e51ab4c5a46f26c28 Mon Sep 17 00:00:00 2001 From: Filip Sajdak Date: Sat, 31 Aug 2024 00:33:44 +0200 Subject: [PATCH 5/5] Add test for as with typesafety --- .../mixed-as-with-typesafety.cpp2 | 32 ++++++++++ .../mixed-as-with-typesafety.cpp.execution | 3 + .../test-results/mixed-as-with-typesafety.cpp | 63 +++++++++++++++++++ .../mixed-as-with-typesafety.cpp2.output | 2 + 4 files changed, 100 insertions(+) create mode 100644 regression-tests/mixed-as-with-typesafety.cpp2 create mode 100644 regression-tests/test-results/apple-clang-14-c++2b/mixed-as-with-typesafety.cpp.execution create mode 100644 regression-tests/test-results/mixed-as-with-typesafety.cpp create mode 100644 regression-tests/test-results/mixed-as-with-typesafety.cpp2.output diff --git a/regression-tests/mixed-as-with-typesafety.cpp2 b/regression-tests/mixed-as-with-typesafety.cpp2 new file mode 100644 index 0000000000..3ee64879ea --- /dev/null +++ b/regression-tests/mixed-as-with-typesafety.cpp2 @@ -0,0 +1,32 @@ +void throw_error(CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM) { + throw std::runtime_error(std::string("Type safety exception: ") + msg); +} + +make_throwable: (inout cg : cpp2::contract_group) -> _ = { + h := cg.get_handler(); + sh := :(pcg : *cpp2::contract_group) = { + pcg*.set_handler(h$); + }; + cg.set_handler(throw_error); + return std::unique_ptr(cg&, sh); +} + +void expect_no_throw(auto&& fun) try { + fun(); +} catch(std::exception const& e) { + std::cout << e.what() << std::endl; +} catch(...) { + std::cout << "Unknown exception!" << std::endl; +} + +main: () = { + o : std::optional = (); + + (_ := make_throwable(cpp2::type_safety)) { + expect_no_throw( :() = { + std::cout << (o$ as int) << std::endl; // that will throw + }); + } + + std::cout << (o as int) << std::endl; // that will terminate +} \ No newline at end of file diff --git a/regression-tests/test-results/apple-clang-14-c++2b/mixed-as-with-typesafety.cpp.execution b/regression-tests/test-results/apple-clang-14-c++2b/mixed-as-with-typesafety.cpp.execution new file mode 100644 index 0000000000..d67092806b --- /dev/null +++ b/regression-tests/test-results/apple-clang-14-c++2b/mixed-as-with-typesafety.cpp.execution @@ -0,0 +1,3 @@ +Type safety exception: 'as' cast failed for 'std::optional' +Type safety violation: 'as' cast failed for 'std::optional' +libc++abi: terminating diff --git a/regression-tests/test-results/mixed-as-with-typesafety.cpp b/regression-tests/test-results/mixed-as-with-typesafety.cpp new file mode 100644 index 0000000000..0da226f2fb --- /dev/null +++ b/regression-tests/test-results/mixed-as-with-typesafety.cpp @@ -0,0 +1,63 @@ + + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "mixed-as-with-typesafety.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "mixed-as-with-typesafety.cpp2" +void throw_error(CPP2_MESSAGE_PARAM msg CPP2_SOURCE_LOCATION_PARAM) { + throw std::runtime_error(std::string("Type safety exception: ") + msg); +} + +#line 5 "mixed-as-with-typesafety.cpp2" +[[nodiscard]] auto make_throwable(cpp2::contract_group& cg) -> auto; +#line 13 "mixed-as-with-typesafety.cpp2" + +void expect_no_throw(auto&& fun) try { + fun(); +} catch(std::exception const& e) { + std::cout << e.what() << std::endl; +} catch(...) { + std::cout << "Unknown exception!" << std::endl; +} + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "mixed-as-with-typesafety.cpp2" + +#line 5 "mixed-as-with-typesafety.cpp2" +[[nodiscard]] auto make_throwable(cpp2::contract_group& cg) -> auto{ + auto h {CPP2_UFCS(get_handler)(cg)}; + auto sh {[_0 = cpp2::move(h)](cpp2::contract_group* pcg) mutable -> void{ + CPP2_UFCS(set_handler)((*cpp2::impl::assert_not_null(pcg)), _0); + }}; + CPP2_UFCS(set_handler)(cg, throw_error); + return std::unique_ptr(&cg, cpp2::move(sh)); +} + +#line 22 "mixed-as-with-typesafety.cpp2" +auto main() -> int{ + std::optional o {}; +{ +[[maybe_unused]] auto const& unnamed_param_1{make_throwable(cpp2::type_safety)}; + +#line 25 "mixed-as-with-typesafety.cpp2" + { + expect_no_throw([_0 = o]() mutable -> void{ + std::cout << (cpp2::impl::as_(_0)) << std::endl;// that will throw + }); + } +} + +#line 31 "mixed-as-with-typesafety.cpp2" + std::cout << (cpp2::impl::as_(cpp2::move(o))) << std::endl;// that will terminate +} + diff --git a/regression-tests/test-results/mixed-as-with-typesafety.cpp2.output b/regression-tests/test-results/mixed-as-with-typesafety.cpp2.output new file mode 100644 index 0000000000..df8f873de1 --- /dev/null +++ b/regression-tests/test-results/mixed-as-with-typesafety.cpp2.output @@ -0,0 +1,2 @@ +mixed-as-with-typesafety.cpp2... ok (mixed Cpp1/Cpp2, Cpp2 code passes safety checks) +