Skip to content

Commit 28f2215

Browse files
committed
Finish merging hsutter#106: Now static_assert for disallowed as cases including bad narrowing, closes hsutter#106
1 parent 0cf6967 commit 28f2215

File tree

6 files changed

+96
-21
lines changed

6 files changed

+96
-21
lines changed

include/cpp2util.h

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ struct nonesuch_ {
702702
};
703703
static nonesuch_ nonesuch;
704704

705-
// Most of the 'as' casts are <To, From> so use that order here
705+
// The 'as' cast functions are <To, From> so use that order here
706706
// If it's confusing, we can switch this to <From, To>
707707
template< typename To, typename From >
708708
inline constexpr auto is_narrowing_v =
@@ -747,12 +747,9 @@ template< typename C, auto x >
747747
inline constexpr auto as() -> auto
748748
{
749749
if constexpr ( is_castable_v<C, x> ) {
750-
return static_cast<C>(CPP2_TYPEOF(x)(x));
750+
return static_cast<C>(x);
751751
} else {
752-
static_assert(
753-
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
754-
"No safe 'as' cast available - if this is narrowing and you're sure the conversion is safe, consider using `unsafe_narrow<T>()` to force the conversion"
755-
);
752+
return nonesuch;
756753
}
757754
}
758755

@@ -764,10 +761,7 @@ inline constexpr auto as(auto const& x) -> auto
764761
sizeof(CPP2_TYPEOF(x)) > sizeof(C)
765762
)
766763
{
767-
static_assert(
768-
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
769-
"No safe 'as' cast from larger to smaller floating point precision - if you're sure you want this unsafe conversion, consider using `unsafe_narrow<T>()` to force the conversion"
770-
);
764+
return nonesuch;
771765
}
772766

773767
// Signed/unsigned conversions to a not-smaller type are handled as a precondition,
@@ -805,10 +799,7 @@ auto as( X const& x ) -> auto
805799
// those types themselves don't defend against them
806800
if constexpr( requires{ typename C::value_type; } && std::is_convertible_v<X, typename C::value_type> ) {
807801
if constexpr( is_narrowing_v<typename C::value_type, X>) {
808-
static_assert(
809-
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
810-
"This is a narrowing 'as' cast to a dynamic library type - if you're sure you want this unsafe conversion, consider adding an `unsafe_narrow<T>()` separately first to force the narrowing conversion, then 'as' to the library type"
811-
);
802+
return nonesuch;
812803
}
813804
}
814805
return C{x};
@@ -1296,6 +1287,64 @@ auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
12961287
return static_cast<C>(CPP2_FORWARD(x));
12971288
}
12981289

1290+
1291+
//-----------------------------------------------------------------------
1292+
//
1293+
// A static-asserting "as" for better diagnostics than raw 'nonesuch'
1294+
//
1295+
// Note for the future: This needs go after all 'as', which is fine for
1296+
// the ones in this file but will have problems with further user-
1297+
// defined 'as' customizations. One solution would be to make the main
1298+
// 'as' be a class template, and have all customizations be actual
1299+
// specializations... that way name lookup should find the primary
1300+
// template first and then see later specializations. Or we could just
1301+
// remove this and live with the 'nonesuch' error messages. Either way,
1302+
// we don't need anything more right now, this solution is fine to
1303+
// unblock general progress
1304+
//
1305+
//-----------------------------------------------------------------------
1306+
//
1307+
template< typename C >
1308+
inline constexpr auto as_( auto&& x ) -> auto
1309+
{
1310+
if constexpr (is_narrowing_v<C, CPP2_TYPEOF(x)>) {
1311+
static_assert(
1312+
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
1313+
"'as' does not allow unsafe narrowing conversions - if you're sure you want this, use `unsafe_narrow<T>()` to force the conversion"
1314+
);
1315+
}
1316+
else if constexpr( std::is_same_v< CPP2_TYPEOF(as<C>(CPP2_FORWARD(x))), nonesuch_ > ) {
1317+
static_assert(
1318+
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
1319+
"No safe 'as' cast available - please check your cast"
1320+
);
1321+
}
1322+
// else
1323+
return as<C>(CPP2_FORWARD(x));
1324+
}
1325+
1326+
template< typename C, auto x >
1327+
inline constexpr auto as_() -> auto
1328+
{
1329+
if constexpr (requires { as<C, x>(); }) {
1330+
if constexpr( std::is_same_v< CPP2_TYPEOF((as<C, x>())), nonesuch_ > ) {
1331+
static_assert(
1332+
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
1333+
"Literal cannot be narrowed using 'as' - if you're sure you want this, use 'unsafe_narrow<T>()' to force the conversion"
1334+
);
1335+
}
1336+
}
1337+
else {
1338+
static_assert(
1339+
program_violates_type_safety_guarantee<C, CPP2_TYPEOF(x)>,
1340+
"No safe 'as' cast available - please check your cast"
1341+
);
1342+
}
1343+
// else
1344+
return as<C,x>();
1345+
}
1346+
1347+
12991348
}
13001349

13011350

regression-tests/test-results/mixed-inspect-values.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ auto test(auto const& x) -> void;
3030

3131
std::any a { 0 };
3232
test(a);
33-
a = cpp2::as<std::string>("plugh");
33+
a = cpp2::as_<std::string>("plugh");
3434
test(std::move(a));
3535

3636
test(0);
3737
test(1);
3838
test(2);
3939
test(3);
4040
test(-42);
41-
test(cpp2::as<std::string>("xyzzy"));
41+
test(cpp2::as_<std::string>("xyzzy"));
4242
test(3.14);
4343
}
4444

regression-tests/test-results/pure2-inspect-expression-in-generic-function-multiple-types.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ auto test_generic(auto const& x, auto const& msg) -> void;
1313
#line 1 "pure2-inspect-expression-in-generic-function-multiple-types.cpp2"
1414
[[nodiscard]] auto main() -> int{
1515
std::variant<int,int,double> v { 42.0 };
16-
std::any a { cpp2::as<std::string>("xyzzy") };
16+
std::any a { cpp2::as_<std::string>("xyzzy") };
1717
std::optional<int> o { };
1818

1919
test_generic(3.14, "double");

regression-tests/test-results/pure2-inspect-fallback-with-variant-any-optional.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ auto test_generic(auto const& x, auto const& msg) -> void;
1313
#line 1 "pure2-inspect-fallback-with-variant-any-optional.cpp2"
1414

1515
[[nodiscard]] auto main() -> int{
16-
std::variant<int,int,std::string> v { cpp2::as<std::string>("xyzzy") };
17-
std::any a { cpp2::as<std::string>("xyzzy") };
18-
std::optional<std::string> o { cpp2::as<std::string>("xyzzy") };
16+
std::variant<int,int,std::string> v { cpp2::as_<std::string>("xyzzy") };
17+
std::any a { cpp2::as_<std::string>("xyzzy") };
18+
std::optional<std::string> o { cpp2::as_<std::string>("xyzzy") };
1919

2020
std::cout << "\nAll these cases satisfy \"matches std::string\"\n";
2121

source/common.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,26 @@ auto strip_path(std::string const& file) -> std::string
265265
}
266266

267267

268+
//-----------------------------------------------------------------------
269+
//
270+
// Misc helpers
271+
//
272+
//-----------------------------------------------------------------------
273+
//
274+
auto replace_all(std::string& s, std::string_view what, std::string_view with)
275+
{
276+
for (
277+
std::string::size_type pos{};
278+
s.npos != (pos = s.find(what.data(), pos, what.length()));
279+
pos += with.length()
280+
)
281+
{
282+
s.replace(pos, what.length(), with.data(), with.length());
283+
}
284+
return s;
285+
}
286+
287+
268288
//-----------------------------------------------------------------------
269289
//
270290
// Command line handling

source/cppfront.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,8 @@ class cppfront
13591359
statement.pop_back();
13601360
}
13611361

1362+
replace_all( statement, "cpp2::as_<", "cpp2::as<" );
1363+
13621364
// If this is an inspect-expression, we'll have to wrap each alternative
13631365
// in an 'if constexpr' so that its type is ignored for mismatches with
13641366
// the inspect-expression's type
@@ -2057,7 +2059,11 @@ class cppfront
20572059
}
20582060
}
20592061
else {
2060-
prefix += "cpp2::" + i->op->to_string(true) + "<" + print_to_string(*i->type) + ">(";
2062+
auto op_name = i->op->to_string(true);
2063+
if (op_name == "as") {
2064+
op_name = "as_"; // use the static_assert-checked 'as' by default...
2065+
} // we'll override this inside inspect-expressions
2066+
prefix += "cpp2::" + op_name + "<" + print_to_string(*i->type) + ">(";
20612067
suffix = ")" + suffix;
20622068
}
20632069
}

0 commit comments

Comments
 (0)