Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is and as support for std::expected (v2) #971

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,93 @@ constexpr auto as( X&& x ) -> decltype(auto) {
}



//-------------------------------------------------------------------------------------------------------------
// std::expected is and as
//
#ifdef __cpp_lib_expected

// is Type
//
template<typename T, typename X>
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
constexpr auto is(X const& x) -> bool
{
return x.has_value();
}

template<typename T, typename U, typename V>
requires std::is_same_v<T, empty>
constexpr auto is(std::expected<U, V> const& x) -> bool
{
return !x.has_value();
}

// is std::unexpected<T> Type
//
template<typename T, typename X>
requires (
std::is_same_v<T, std::unexpected<typename X::error_type>>
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
)
constexpr auto is(X const& x) -> bool
{
return !x.has_value();
}


// is Value
//
template<typename T, typename U>
constexpr auto is(std::expected<T, U> const& x, auto&& value) -> bool
{
// Predicate case
if constexpr (requires{ bool{ value(x) }; }) {
return value(x);
}
else if constexpr (std::is_function_v<decltype(value)> || requires{ &value.operator(); }) {
return false;
}

// Value case
else if constexpr (requires{ bool{ x.value() == value }; }) {
return x.has_value() && x.value() == value;
}
else {
return false;
}
}


// as
//
template<typename T, typename X>
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
constexpr auto as(X const& x) -> decltype(auto)
{
return x.value();
}

// as std::unexpected<T>
//
template<typename T, typename X>
requires (
std::is_same_v<T, std::unexpected<typename X::error_type>>
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
)
constexpr auto as(X const& x) -> decltype(auto)
{
// It's UB to call `error` if `has_value` is true.
if (x.has_value()) {
Throw(
std::runtime_error("Cannot cast 'expected' to 'unexpected' because it has a value"),
"Cannot cast 'expected' to 'unexpected' because it has a value");
}

return std::unexpected<typename X::error_type>(x.error());
}
#endif

} // impl


Expand Down
90 changes: 90 additions & 0 deletions regression-tests/pure2-expected-is-as.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// `std::expected` requires C++23 so a dedicated test file is needed
// since only MSVC supports it at time of writing, and there's no #ifdef
// or `static if` support in Cpp2 (yet?).

main: () -> int = {

ex1: std::expected<int, int> = (123);
ex2: std::expected<int, int> = std::unexpected(-1);
ex3: std::expected<std::string, size_t> = ("Expect the unexpected");

if ex1 is int {
std::cout << "ex1 is int\n";
}

if ex1 is bool {
std::cout << "BUG - ex1 is not a bool\n";
return -1;
}

if ex1 is void {
std::cout << "BUG - ex1 is not 'empty'\n";
return -1;
}

if ex1 is std::unexpected<int> {
std::cout << "BUG - ex1 is not unexpected\n";
return -1;
}

if ex1 is 123 {
std::cout << "ex1 is 123\n";
}

if ex1 is 100 {
std::cout << "BUG - ex1's value is not 100\n";
return -1;
}

val1:= ex1 as int;
std::cout << "ex1 as int = " << val1 << "\n";

if ex2 is int {
std::cout << "BUG - ex2 is not an int\n";
return -1;
}

if ex2 is bool {
std::cout << "BUG - ex2 is not a bool\n";
return -1;
}

if ex2 is 123 {
std::cout << "BUG - ex2 does not have a value\n";
return -1;
}

if ex2 is std::unexpected<int> {
std::cout << "ex2 is unexpected<int> and error is: " << ex2.error() << "\n";
}

if ex2 is void {
std::cout << "ex2 is 'empty' aka unexpected<int> and error is: " << ex2.error() << "\n";
}

ex2_err:= ex2 as std::unexpected<int>;
std::cout << "ex2 as std::unexpected<int> and error = " << ex2_err.error() << "\n";

test_inspect(ex1, "expected<int, int> with value");
test_inspect(ex2, "expected<int, int> with unexpected");
test_inspect(ex3, "expected<string, size_t> with value");

return 0;
}

test_inspect: ( x: _, msg: _ ) = {

unwrap:= :(unexp: std::unexpected<int>) -> _ = {
return unexp.error();
};

std::cout
<< "\n" << msg << "\n ..."
<< inspect x -> std::string {
is int = "integer " + std::to_string(x as int);
is std::unexpected<int> = "unexpected<int> " + std::to_string(unwrap(x as std::unexpected<int>));
is std::string = "string " + x as std::string;
is _ = " no match";
}
<< "\n";
}
53 changes: 33 additions & 20 deletions regression-tests/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,28 +135,11 @@ if [ -z "$label" ]; then
usage
fi

tests=$(ls | grep ".cpp2$")
if [[ -n "$chosen_tests" ]]; then
for test in $chosen_tests; do
if ! [[ -f "$test" ]]; then
echo "Requested test ($test) not found"
exit 1
fi
done
echo "Performing tests:"
for test in $chosen_tests; do
echo " $test"
done
echo
tests="$chosen_tests"
else
printf "Performing all regression tests\n\n"
fi

expected_results_dir="test-results"

################
# Get the directory with the exec outputs and compilation command
# We also allow each compiler configuration to specify any test files(s) to exclude from running.
expected_results_dir="test-results"
exclude_test_filter=""
if [[ "$cxx_compiler" == *"cl.exe"* ]]; then
compiler_cmd="cl.exe -nologo -std:${cxx_std} -MD -EHsc -I ..\..\..\include -Fe:"
exec_out_dir="$expected_results_dir/msvc-2022-${cxx_std}"
Expand All @@ -174,6 +157,12 @@ else
if [[ "$compiler_version" == *"Apple clang version 14.0"* ||
"$compiler_version" == *"Homebrew clang version 15.0"* ]]; then
exec_out_dir="$expected_results_dir/apple-clang-14"
# We share the expected results dir for these two compilers, but there is one
# test which (as expected) fails to compile on both compilers, but has a slightly
# different error diagnostic because the clang path differs. So we exclude it from
# running. The alternative would be to duplicate the expected results files, which
# seems wasteful for just one test (that doesn't even compile).
exclude_test_filter="pure2-expected-is-as.cpp2"
elif [[ "$compiler_version" == *"Apple clang version 15.0"* ]]; then
exec_out_dir="$expected_results_dir/apple-clang-15"
elif [[ "$compiler_version" == *"clang version 12.0"* ]]; then
Expand Down Expand Up @@ -236,6 +225,30 @@ else
exit 2
fi

################
# Get the list of .cpp2 test files
if [[ -n "$exclude_test_filter" ]]; then
tests=$(ls | grep ".cpp2$" | grep -v $exclude_test_filter)
else
tests=$(ls | grep ".cpp2$")
fi
if [[ -n "$chosen_tests" ]]; then
for test in $chosen_tests; do
if ! [[ -f "$test" ]]; then
echo "Requested test ($test) not found"
exit 1
fi
done
echo "Performing tests:"
for test in $chosen_tests; do
echo " $test"
done
echo
tests="$chosen_tests"
else
printf "Performing all regression tests\n\n"
fi

################
cppfront_cmd="cppfront.exe"
echo "Building cppfront"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
pure2-expected-is-as.cpp2:7:10: error: no member named 'expected' in namespace 'std'
std::expected<int,int> ex1 {123};
~~~~~^
pure2-expected-is-as.cpp2:7:22: error: expected '(' for function-style cast or type construction
std::expected<int,int> ex1 {123};
~~~^
pure2-expected-is-as.cpp2:8:10: error: no member named 'expected' in namespace 'std'
std::expected<int,int> ex2 {std::unexpected(-1)};
~~~~~^
pure2-expected-is-as.cpp2:8:22: error: expected '(' for function-style cast or type construction
std::expected<int,int> ex2 {std::unexpected(-1)};
~~~^
pure2-expected-is-as.cpp2:9:10: error: no member named 'expected' in namespace 'std'
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
~~~~~^
pure2-expected-is-as.cpp2:9:30: error: expected '(' for function-style cast or type construction
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
~~~~~~~~~~~^
pure2-expected-is-as.cpp2:11:29: error: use of undeclared identifier 'ex1'
if (cpp2::impl::is<int>(ex1)) {
^
pure2-expected-is-as.cpp2:15:30: error: use of undeclared identifier 'ex1'
if (cpp2::impl::is<bool>(ex1)) {
^
pure2-expected-is-as.cpp2:20:30: error: use of undeclared identifier 'ex1'
if (cpp2::impl::is<void>(ex1)) {
^
pure2-expected-is-as.cpp2:25:29: error: no member named 'unexpected' in namespace 'std'
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
~~~~~^
pure2-expected-is-as.cpp2:25:43: error: expected '(' for function-style cast or type construction
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
~~~^
pure2-expected-is-as.cpp2:25:46: error: use of undeclared identifier 'ex1'; did you mean 'exp'?
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
^~~
exp
/Applications/Xcode_14.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/math.h:895:1: note: 'exp' declared here
exp(_A1 __lcpp_x) _NOEXCEPT {return ::exp((double)__lcpp_x);}
^
pure2-expected-is-as.cpp2:30:24: error: use of undeclared identifier 'ex1'
if (cpp2::impl::is(ex1, 123)) {
^
pure2-expected-is-as.cpp2:34:24: error: use of undeclared identifier 'ex1'
if (cpp2::impl::is(ex1, 100)) {
^
pure2-expected-is-as.cpp2:39:37: error: use of undeclared identifier 'ex1'
auto val1 {cpp2::impl::as_<int>(ex1)};
^
pure2-expected-is-as.cpp2:42:29: error: use of undeclared identifier 'ex2'
if (cpp2::impl::is<int>(ex2)) {
^
pure2-expected-is-as.cpp2:47:30: error: use of undeclared identifier 'ex2'
if (cpp2::impl::is<bool>(ex2)) {
^
pure2-expected-is-as.cpp2:52:24: error: use of undeclared identifier 'ex2'
if (cpp2::impl::is(ex2, 123)) {
^
pure2-expected-is-as.cpp2:57:29: error: no member named 'unexpected' in namespace 'std'
if (cpp2::impl::is<std::unexpected<int>>(ex2)) {
~~~~~^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ex1 is int
ex1 is 123
ex1 as int = 123
ex2 is unexpected<int> and error is: -1
ex2 is 'empty' aka unexpected<int> and error is: -1
ex2 as std::unexpected<int> and error = -1

expected<int, int> with value
...integer 123

expected<int, int> with unexpected
...unexpected<int> -1

expected<string, size_t> with value
...string Expect the unexpected
Loading
Loading