Skip to content

Commit a7f8144

Browse files
Add is and as support for std::expected (v2) (#971)
* Add `is` and `as` support for `std::expected` * Add test-results for `pure2-expected-is-as` test case * Update test-results * [CI] run-tests.sh now allows each compiler config to exclude test files that it doesn't want to run There are 2 macOS test configs which share the same expected results directory (to avoid duplication). This works great except for the new `pure2-expected-is-as` test. This new test code fails to compile (as expected) on both compilers, but produces a slightly different error diagnostic because the path is different. E.g. /Applications/Xcode_14.3.1.app/...etc.../math.h and /Library/Developer/CommandLineTools/...etc.../math.h One option would be to stop sharing the expected results for both of these compilers, but that seems wasteful since it's just one test which fails to compile. So instead the `run-tests` script has a new way to exclude a test from running.
1 parent 166e8af commit a7f8144

18 files changed

+1296
-20
lines changed

include/cpp2util.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2139,6 +2139,93 @@ constexpr auto as( X&& x ) -> decltype(auto) {
21392139
}
21402140

21412141

2142+
2143+
//-------------------------------------------------------------------------------------------------------------
2144+
// std::expected is and as
2145+
//
2146+
#ifdef __cpp_lib_expected
2147+
2148+
// is Type
2149+
//
2150+
template<typename T, typename X>
2151+
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
2152+
constexpr auto is(X const& x) -> bool
2153+
{
2154+
return x.has_value();
2155+
}
2156+
2157+
template<typename T, typename U, typename V>
2158+
requires std::is_same_v<T, empty>
2159+
constexpr auto is(std::expected<U, V> const& x) -> bool
2160+
{
2161+
return !x.has_value();
2162+
}
2163+
2164+
// is std::unexpected<T> Type
2165+
//
2166+
template<typename T, typename X>
2167+
requires (
2168+
std::is_same_v<T, std::unexpected<typename X::error_type>>
2169+
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
2170+
)
2171+
constexpr auto is(X const& x) -> bool
2172+
{
2173+
return !x.has_value();
2174+
}
2175+
2176+
2177+
// is Value
2178+
//
2179+
template<typename T, typename U>
2180+
constexpr auto is(std::expected<T, U> const& x, auto&& value) -> bool
2181+
{
2182+
// Predicate case
2183+
if constexpr (requires{ bool{ value(x) }; }) {
2184+
return value(x);
2185+
}
2186+
else if constexpr (std::is_function_v<decltype(value)> || requires{ &value.operator(); }) {
2187+
return false;
2188+
}
2189+
2190+
// Value case
2191+
else if constexpr (requires{ bool{ x.value() == value }; }) {
2192+
return x.has_value() && x.value() == value;
2193+
}
2194+
else {
2195+
return false;
2196+
}
2197+
}
2198+
2199+
2200+
// as
2201+
//
2202+
template<typename T, typename X>
2203+
requires std::is_same_v<X, std::expected<T, typename X::error_type>>
2204+
constexpr auto as(X const& x) -> decltype(auto)
2205+
{
2206+
return x.value();
2207+
}
2208+
2209+
// as std::unexpected<T>
2210+
//
2211+
template<typename T, typename X>
2212+
requires (
2213+
std::is_same_v<T, std::unexpected<typename X::error_type>>
2214+
&& std::is_same_v<X, std::expected<typename X::value_type, typename X::error_type>>
2215+
)
2216+
constexpr auto as(X const& x) -> decltype(auto)
2217+
{
2218+
// It's UB to call `error` if `has_value` is true.
2219+
if (x.has_value()) {
2220+
Throw(
2221+
std::runtime_error("Cannot cast 'expected' to 'unexpected' because it has a value"),
2222+
"Cannot cast 'expected' to 'unexpected' because it has a value");
2223+
}
2224+
2225+
return std::unexpected<typename X::error_type>(x.error());
2226+
}
2227+
#endif
2228+
21422229
} // impl
21432230

21442231

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// `std::expected` requires C++23 so a dedicated test file is needed
2+
// since only MSVC supports it at time of writing, and there's no #ifdef
3+
// or `static if` support in Cpp2 (yet?).
4+
5+
main: () -> int = {
6+
7+
ex1: std::expected<int, int> = (123);
8+
ex2: std::expected<int, int> = std::unexpected(-1);
9+
ex3: std::expected<std::string, size_t> = ("Expect the unexpected");
10+
11+
if ex1 is int {
12+
std::cout << "ex1 is int\n";
13+
}
14+
15+
if ex1 is bool {
16+
std::cout << "BUG - ex1 is not a bool\n";
17+
return -1;
18+
}
19+
20+
if ex1 is void {
21+
std::cout << "BUG - ex1 is not 'empty'\n";
22+
return -1;
23+
}
24+
25+
if ex1 is std::unexpected<int> {
26+
std::cout << "BUG - ex1 is not unexpected\n";
27+
return -1;
28+
}
29+
30+
if ex1 is 123 {
31+
std::cout << "ex1 is 123\n";
32+
}
33+
34+
if ex1 is 100 {
35+
std::cout << "BUG - ex1's value is not 100\n";
36+
return -1;
37+
}
38+
39+
val1:= ex1 as int;
40+
std::cout << "ex1 as int = " << val1 << "\n";
41+
42+
if ex2 is int {
43+
std::cout << "BUG - ex2 is not an int\n";
44+
return -1;
45+
}
46+
47+
if ex2 is bool {
48+
std::cout << "BUG - ex2 is not a bool\n";
49+
return -1;
50+
}
51+
52+
if ex2 is 123 {
53+
std::cout << "BUG - ex2 does not have a value\n";
54+
return -1;
55+
}
56+
57+
if ex2 is std::unexpected<int> {
58+
std::cout << "ex2 is unexpected<int> and error is: " << ex2.error() << "\n";
59+
}
60+
61+
if ex2 is void {
62+
std::cout << "ex2 is 'empty' aka unexpected<int> and error is: " << ex2.error() << "\n";
63+
}
64+
65+
ex2_err:= ex2 as std::unexpected<int>;
66+
std::cout << "ex2 as std::unexpected<int> and error = " << ex2_err.error() << "\n";
67+
68+
test_inspect(ex1, "expected<int, int> with value");
69+
test_inspect(ex2, "expected<int, int> with unexpected");
70+
test_inspect(ex3, "expected<string, size_t> with value");
71+
72+
return 0;
73+
}
74+
75+
test_inspect: ( x: _, msg: _ ) = {
76+
77+
unwrap:= :(unexp: std::unexpected<int>) -> _ = {
78+
return unexp.error();
79+
};
80+
81+
std::cout
82+
<< "\n" << msg << "\n ..."
83+
<< inspect x -> std::string {
84+
is int = "integer " + std::to_string(x as int);
85+
is std::unexpected<int> = "unexpected<int> " + std::to_string(unwrap(x as std::unexpected<int>));
86+
is std::string = "string " + x as std::string;
87+
is _ = " no match";
88+
}
89+
<< "\n";
90+
}

regression-tests/run-tests.sh

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,28 +135,11 @@ if [ -z "$label" ]; then
135135
usage
136136
fi
137137

138-
tests=$(ls | grep ".cpp2$")
139-
if [[ -n "$chosen_tests" ]]; then
140-
for test in $chosen_tests; do
141-
if ! [[ -f "$test" ]]; then
142-
echo "Requested test ($test) not found"
143-
exit 1
144-
fi
145-
done
146-
echo "Performing tests:"
147-
for test in $chosen_tests; do
148-
echo " $test"
149-
done
150-
echo
151-
tests="$chosen_tests"
152-
else
153-
printf "Performing all regression tests\n\n"
154-
fi
155-
156-
expected_results_dir="test-results"
157-
158138
################
159139
# Get the directory with the exec outputs and compilation command
140+
# We also allow each compiler configuration to specify any test files(s) to exclude from running.
141+
expected_results_dir="test-results"
142+
exclude_test_filter=""
160143
if [[ "$cxx_compiler" == *"cl.exe"* ]]; then
161144
compiler_cmd="cl.exe -nologo -std:${cxx_std} -MD -EHsc -I ..\..\..\include -Fe:"
162145
exec_out_dir="$expected_results_dir/msvc-2022-${cxx_std}"
@@ -174,6 +157,12 @@ else
174157
if [[ "$compiler_version" == *"Apple clang version 14.0"* ||
175158
"$compiler_version" == *"Homebrew clang version 15.0"* ]]; then
176159
exec_out_dir="$expected_results_dir/apple-clang-14"
160+
# We share the expected results dir for these two compilers, but there is one
161+
# test which (as expected) fails to compile on both compilers, but has a slightly
162+
# different error diagnostic because the clang path differs. So we exclude it from
163+
# running. The alternative would be to duplicate the expected results files, which
164+
# seems wasteful for just one test (that doesn't even compile).
165+
exclude_test_filter="pure2-expected-is-as.cpp2"
177166
elif [[ "$compiler_version" == *"Apple clang version 15.0"* ]]; then
178167
exec_out_dir="$expected_results_dir/apple-clang-15"
179168
elif [[ "$compiler_version" == *"clang version 12.0"* ]]; then
@@ -236,6 +225,30 @@ else
236225
exit 2
237226
fi
238227

228+
################
229+
# Get the list of .cpp2 test files
230+
if [[ -n "$exclude_test_filter" ]]; then
231+
tests=$(ls | grep ".cpp2$" | grep -v $exclude_test_filter)
232+
else
233+
tests=$(ls | grep ".cpp2$")
234+
fi
235+
if [[ -n "$chosen_tests" ]]; then
236+
for test in $chosen_tests; do
237+
if ! [[ -f "$test" ]]; then
238+
echo "Requested test ($test) not found"
239+
exit 1
240+
fi
241+
done
242+
echo "Performing tests:"
243+
for test in $chosen_tests; do
244+
echo " $test"
245+
done
246+
echo
247+
tests="$chosen_tests"
248+
else
249+
printf "Performing all regression tests\n\n"
250+
fi
251+
239252
################
240253
cppfront_cmd="cppfront.exe"
241254
echo "Building cppfront"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
pure2-expected-is-as.cpp2:7:10: error: no member named 'expected' in namespace 'std'
2+
std::expected<int,int> ex1 {123};
3+
~~~~~^
4+
pure2-expected-is-as.cpp2:7:22: error: expected '(' for function-style cast or type construction
5+
std::expected<int,int> ex1 {123};
6+
~~~^
7+
pure2-expected-is-as.cpp2:8:10: error: no member named 'expected' in namespace 'std'
8+
std::expected<int,int> ex2 {std::unexpected(-1)};
9+
~~~~~^
10+
pure2-expected-is-as.cpp2:8:22: error: expected '(' for function-style cast or type construction
11+
std::expected<int,int> ex2 {std::unexpected(-1)};
12+
~~~^
13+
pure2-expected-is-as.cpp2:9:10: error: no member named 'expected' in namespace 'std'
14+
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
15+
~~~~~^
16+
pure2-expected-is-as.cpp2:9:30: error: expected '(' for function-style cast or type construction
17+
std::expected<std::string,size_t> ex3 {"Expect the unexpected"};
18+
~~~~~~~~~~~^
19+
pure2-expected-is-as.cpp2:11:29: error: use of undeclared identifier 'ex1'
20+
if (cpp2::impl::is<int>(ex1)) {
21+
^
22+
pure2-expected-is-as.cpp2:15:30: error: use of undeclared identifier 'ex1'
23+
if (cpp2::impl::is<bool>(ex1)) {
24+
^
25+
pure2-expected-is-as.cpp2:20:30: error: use of undeclared identifier 'ex1'
26+
if (cpp2::impl::is<void>(ex1)) {
27+
^
28+
pure2-expected-is-as.cpp2:25:29: error: no member named 'unexpected' in namespace 'std'
29+
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
30+
~~~~~^
31+
pure2-expected-is-as.cpp2:25:43: error: expected '(' for function-style cast or type construction
32+
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
33+
~~~^
34+
pure2-expected-is-as.cpp2:25:46: error: use of undeclared identifier 'ex1'; did you mean 'exp'?
35+
if (cpp2::impl::is<std::unexpected<int>>(ex1)) {
36+
^~~
37+
exp
38+
/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
39+
exp(_A1 __lcpp_x) _NOEXCEPT {return ::exp((double)__lcpp_x);}
40+
^
41+
pure2-expected-is-as.cpp2:30:24: error: use of undeclared identifier 'ex1'
42+
if (cpp2::impl::is(ex1, 123)) {
43+
^
44+
pure2-expected-is-as.cpp2:34:24: error: use of undeclared identifier 'ex1'
45+
if (cpp2::impl::is(ex1, 100)) {
46+
^
47+
pure2-expected-is-as.cpp2:39:37: error: use of undeclared identifier 'ex1'
48+
auto val1 {cpp2::impl::as_<int>(ex1)};
49+
^
50+
pure2-expected-is-as.cpp2:42:29: error: use of undeclared identifier 'ex2'
51+
if (cpp2::impl::is<int>(ex2)) {
52+
^
53+
pure2-expected-is-as.cpp2:47:30: error: use of undeclared identifier 'ex2'
54+
if (cpp2::impl::is<bool>(ex2)) {
55+
^
56+
pure2-expected-is-as.cpp2:52:24: error: use of undeclared identifier 'ex2'
57+
if (cpp2::impl::is(ex2, 123)) {
58+
^
59+
pure2-expected-is-as.cpp2:57:29: error: no member named 'unexpected' in namespace 'std'
60+
if (cpp2::impl::is<std::unexpected<int>>(ex2)) {
61+
~~~~~^
62+
fatal error: too many errors emitted, stopping now [-ferror-limit=]
63+
20 errors generated.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ex1 is int
2+
ex1 is 123
3+
ex1 as int = 123
4+
ex2 is unexpected<int> and error is: -1
5+
ex2 is 'empty' aka unexpected<int> and error is: -1
6+
ex2 as std::unexpected<int> and error = -1
7+
8+
expected<int, int> with value
9+
...integer 123
10+
11+
expected<int, int> with unexpected
12+
...unexpected<int> -1
13+
14+
expected<string, size_t> with value
15+
...string Expect the unexpected

0 commit comments

Comments
 (0)