From 698b44c4243e0290621ff78ab53422d201e5ecd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Wed, 9 Oct 2024 12:30:52 -0400 Subject: [PATCH] feat: recognize `requires` expressions --- ...pure2-concept-definition-no-pitfall-1.cpp2 | 7 + ...pure2-concept-definition-no-pitfall-2.cpp2 | 8 + ...pure2-concept-definition-no-pitfall-3.cpp2 | 7 + ...2-concept-definition-pit-of-success-1.cpp2 | 22 + .../pure2-concept-definition.cpp2 | 30 +- regression-tests/pure2-print.cpp2 | 13 + regression-tests/pure2-requires-clauses.cpp2 | 2 + .../pure2-concept-definition-no-pitfall-1.cpp | 30 ++ ...oncept-definition-no-pitfall-1.cpp2.output | 2 + .../pure2-concept-definition-no-pitfall-2.cpp | 31 ++ ...oncept-definition-no-pitfall-2.cpp2.output | 2 + .../pure2-concept-definition-no-pitfall-3.cpp | 30 ++ ...oncept-definition-no-pitfall-3.cpp2.output | 2 + ...e2-concept-definition-pit-of-success-1.cpp | 64 +++ ...pt-definition-pit-of-success-1.cpp2.output | 2 + .../test-results/pure2-concept-definition.cpp | 34 +- regression-tests/test-results/pure2-print.cpp | 33 +- .../test-results/pure2-print.cpp2.output | 9 + .../test-results/pure2-requires-clauses.cpp | 8 + source/parse.h | 468 +++++++++++++++++- source/sema.h | 11 +- source/to_cpp1.h | 92 ++++ 22 files changed, 870 insertions(+), 37 deletions(-) create mode 100644 regression-tests/pure2-concept-definition-no-pitfall-1.cpp2 create mode 100644 regression-tests/pure2-concept-definition-no-pitfall-2.cpp2 create mode 100644 regression-tests/pure2-concept-definition-no-pitfall-3.cpp2 create mode 100644 regression-tests/pure2-concept-definition-pit-of-success-1.cpp2 create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp2.output create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp2.output create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp create mode 100644 regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp2.output create mode 100644 regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp create mode 100644 regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp2.output diff --git a/regression-tests/pure2-concept-definition-no-pitfall-1.cpp2 b/regression-tests/pure2-concept-definition-no-pitfall-1.cpp2 new file mode 100644 index 000000000..b4af54c9d --- /dev/null +++ b/regression-tests/pure2-concept-definition-no-pitfall-1.cpp2 @@ -0,0 +1,7 @@ +// https://youtu.be/CXn02MPkn8Y?t=2337 +negatable: concept = requires(t: T) +{ + _ = -t is T; // Hopefully obviously wrong. Should be `{ -t } is T;`. +}; + +main: () = static_assert(negatable); diff --git a/regression-tests/pure2-concept-definition-no-pitfall-2.cpp2 b/regression-tests/pure2-concept-definition-no-pitfall-2.cpp2 new file mode 100644 index 000000000..c89a9ebdb --- /dev/null +++ b/regression-tests/pure2-concept-definition-no-pitfall-2.cpp2 @@ -0,0 +1,8 @@ +// https://youtu.be/CXn02MPkn8Y?t=2418 +int_sized: concept = requires(t: T) +{ + _ = sizeof(T) == 4; // Hopefully obviously wrong. Should be `requires (sizeof(T) == 4);`. +}; +// Could also be `int_sized: concept = sizeof(T) == 4;`. + +main: () = static_assert(int_sized); diff --git a/regression-tests/pure2-concept-definition-no-pitfall-3.cpp2 b/regression-tests/pure2-concept-definition-no-pitfall-3.cpp2 new file mode 100644 index 000000000..184e41f47 --- /dev/null +++ b/regression-tests/pure2-concept-definition-no-pitfall-3.cpp2 @@ -0,0 +1,7 @@ +// https://youtu.be/CXn02MPkn8Y?t=2455 +nothrow_incrementable: concept = requires(inout t: T) +{ + _ = noexcept(t++); // Hopefully obviously wrong. Should be `requires noexcept(t++);` or `{ t++ } !throws;`. +}; + +main: () = { static_assert(nothrow_incrementable); } diff --git a/regression-tests/pure2-concept-definition-pit-of-success-1.cpp2 b/regression-tests/pure2-concept-definition-pit-of-success-1.cpp2 new file mode 100644 index 000000000..0a0220fd7 --- /dev/null +++ b/regression-tests/pure2-concept-definition-pit-of-success-1.cpp2 @@ -0,0 +1,22 @@ +// https://quuxplusone.github.io/blog/2021/06/09/another-concepts-chest-mimic/ +has_a_but_not_b: concept = requires(t: T) +{ + _ = a(t); + !requires _ = b(t); // In Cpp2, this works and does the correct thing. +}; + +s1: @struct type = { } +s2: @struct type = { } +a: (_: s2) = { } +s3: @struct type = { } +b: (_: s3) = { } +s4: @struct type = { } +a: (_: s4) = { } +b: (_: s4) = { } + +main: () = { + static_assert(!has_a_but_not_b); // as expected + static_assert(has_a_but_not_b); // as expected + static_assert(!has_a_but_not_b); // as expected + static_assert(!has_a_but_not_b); // pit of success! +} diff --git a/regression-tests/pure2-concept-definition.cpp2 b/regression-tests/pure2-concept-definition.cpp2 index 21572d2e7..be4583265 100644 --- a/regression-tests/pure2-concept-definition.cpp2 +++ b/regression-tests/pure2-concept-definition.cpp2 @@ -1,5 +1,29 @@ arithmetic: concept = std::integral || std::floating_point; -main: () = { - assert( arithmetic ); - assert( arithmetic ); + +number_difference_t: type == std::type_identity_t; +number: concept = std::regular && requires(c: T) +{ + !requires std::iter_reference_t; // Negative requirement. + {c + c} is std::common_with; // Compound requirement. + number_difference_t; // Type requirement. + _ = c - c; // Expression requirement. + requires std::common_with, T>; // Nested requirement. +}; + +test_nonthrowing_requirements: concept = requires +{ // clang-format off + { T() } !throws; + { -T() } !throws, is std::same_as; +}; // clang-format on + +main: () = { + static_assert(arithmetic); + static_assert(arithmetic); + static_assert(number); + static_assert(number); + static_assert(number); + static_assert(!number<* i32>); + static_assert(!number>); + static_assert(test_nonthrowing_requirements); + static_assert(!test_nonthrowing_requirements); } diff --git a/regression-tests/pure2-print.cpp2 b/regression-tests/pure2-print.cpp2 index 6b80d9cb7..37071908e 100644 --- a/regression-tests/pure2-print.cpp2 +++ b/regression-tests/pure2-print.cpp2 @@ -87,6 +87,19 @@ outer: @print type = { is 43 = "forty-and-three"; is _ = "default case"; } << "\n"; + + _ = + requires { std::vector; } + + requires { _ = 0; } + + requires { + requires true; + !requires std::vector; + !requires _ = 0; + { 0 }; + { 0 } !throws; + { 0 } is std::regular; + { 0 } !throws, is std::regular; + }; } x: type = { diff --git a/regression-tests/pure2-requires-clauses.cpp2 b/regression-tests/pure2-requires-clauses.cpp2 index 2d7719c54..d072ba113 100644 --- a/regression-tests/pure2-requires-clauses.cpp2 +++ b/regression-tests/pure2-requires-clauses.cpp2 @@ -19,6 +19,8 @@ f: (x) -> int requires true == x; v: const T requires std::same_as = 0; +g: () requires true = { } + main: () = { _: X = (); std::cout << f(2,5) diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp b/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp new file mode 100644 index 000000000..a1ef603d0 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp @@ -0,0 +1,30 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-concept-definition-no-pitfall-1.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-concept-definition-no-pitfall-1.cpp2" +// https://youtu.be/CXn02MPkn8Y?t=2337 +#line 2 "pure2-concept-definition-no-pitfall-1.cpp2" +template concept negatable = requires(T const& t) { + + cpp2::impl::is(-t); +}; // Hopefully obviously wrong. Should be `{ -t } is T;`. + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-concept-definition-no-pitfall-1.cpp2" + +#line 7 "pure2-concept-definition-no-pitfall-1.cpp2" +auto main() -> int { static_assert(negatable); } + diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp2.output b/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp2.output new file mode 100644 index 000000000..3faf3b4cc --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-1.cpp2.output @@ -0,0 +1,2 @@ +pure2-concept-definition-no-pitfall-1.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp b/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp new file mode 100644 index 000000000..abd177b07 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp @@ -0,0 +1,31 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-concept-definition-no-pitfall-2.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-concept-definition-no-pitfall-2.cpp2" +// https://youtu.be/CXn02MPkn8Y?t=2418 +#line 2 "pure2-concept-definition-no-pitfall-2.cpp2" +template concept int_sized = requires(T const& t) { + + sizeof(T) == 4; +}; // Hopefully obviously wrong. Should be `requires (sizeof(T) == 4);`. +// Could also be `int_sized: concept = sizeof(T) == 4;`. + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-concept-definition-no-pitfall-2.cpp2" + +#line 8 "pure2-concept-definition-no-pitfall-2.cpp2" +auto main() -> int { static_assert(int_sized); } + diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp2.output b/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp2.output new file mode 100644 index 000000000..6d3fdd3a1 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-2.cpp2.output @@ -0,0 +1,2 @@ +pure2-concept-definition-no-pitfall-2.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp b/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp new file mode 100644 index 000000000..2fe3c8d88 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp @@ -0,0 +1,30 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-concept-definition-no-pitfall-3.cpp2" + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-concept-definition-no-pitfall-3.cpp2" +// https://youtu.be/CXn02MPkn8Y?t=2455 +#line 2 "pure2-concept-definition-no-pitfall-3.cpp2" +template concept nothrow_incrementable = requires(T& t) { + + noexcept(++t); +}; // Hopefully obviously wrong. Should be `requires noexcept(t++);` or `{ t++ } !throws;`. + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-concept-definition-no-pitfall-3.cpp2" + +#line 7 "pure2-concept-definition-no-pitfall-3.cpp2" +auto main() -> int{static_assert(nothrow_incrementable); } + diff --git a/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp2.output b/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp2.output new file mode 100644 index 000000000..89b47536f --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-no-pitfall-3.cpp2.output @@ -0,0 +1,2 @@ +pure2-concept-definition-no-pitfall-3.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp b/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp new file mode 100644 index 000000000..48f7bd3e9 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp @@ -0,0 +1,64 @@ + +#define CPP2_IMPORT_STD Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + +#line 1 "pure2-concept-definition-pit-of-success-1.cpp2" + +#line 8 "pure2-concept-definition-pit-of-success-1.cpp2" +class s1; +class s2; + +class s3; + +class s4; + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-concept-definition-pit-of-success-1.cpp2" +// https://quuxplusone.github.io/blog/2021/06/09/another-concepts-chest-mimic/ +#line 2 "pure2-concept-definition-pit-of-success-1.cpp2" +template concept has_a_but_not_b = requires(T const& t) { + + a(t); + requires !requires { b(t); }; +}; // In Cpp2, this works and does the correct thing. + +class s1 {}; +class s2 {}; +auto a([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void; +class s3 {}; +auto b([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void; +class s4 {}; +auto a([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void; +auto b([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void; + +auto main() -> int; + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-concept-definition-pit-of-success-1.cpp2" + +#line 10 "pure2-concept-definition-pit-of-success-1.cpp2" +auto a([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{} + +#line 12 "pure2-concept-definition-pit-of-success-1.cpp2" +auto b([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{} + +#line 14 "pure2-concept-definition-pit-of-success-1.cpp2" +auto a([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{} +#line 15 "pure2-concept-definition-pit-of-success-1.cpp2" +auto b([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{} + +#line 17 "pure2-concept-definition-pit-of-success-1.cpp2" +auto main() -> int{ + static_assert(!(has_a_but_not_b));// as expected + static_assert(has_a_but_not_b); // as expected + static_assert(!(has_a_but_not_b));// as expected + static_assert(!(has_a_but_not_b));// pit of success! +} + diff --git a/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp2.output b/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp2.output new file mode 100644 index 000000000..265113d12 --- /dev/null +++ b/regression-tests/test-results/pure2-concept-definition-pit-of-success-1.cpp2.output @@ -0,0 +1,2 @@ +pure2-concept-definition-pit-of-success-1.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-concept-definition.cpp b/regression-tests/test-results/pure2-concept-definition.cpp index b4222a5da..26a703ecf 100644 --- a/regression-tests/test-results/pure2-concept-definition.cpp +++ b/regression-tests/test-results/pure2-concept-definition.cpp @@ -13,16 +13,40 @@ #line 1 "pure2-concept-definition.cpp2" template concept arithmetic = std::integral || std::floating_point; -#line 2 "pure2-concept-definition.cpp2" + +#line 3 "pure2-concept-definition.cpp2" +template using number_difference_t = std::type_identity_t; +template concept number = std::regular && requires(T const& c) { + + requires !requires { typename std::iter_reference_t; };// Negative requirement. + { c + c } -> std::common_with; // Compound requirement. + typename number_difference_t; // Type requirement. + c - c; // Expression requirement. + requires std::common_with,T>; +}; // Nested requirement. + +template concept test_nonthrowing_requirements = requires { + // clang-format off + { T() } noexcept; + { -T() } noexcept -> std::same_as; +}; // clang-format on + auto main() -> int; //=== Cpp2 function definitions ================================================= #line 1 "pure2-concept-definition.cpp2" -#line 2 "pure2-concept-definition.cpp2" -auto main() -> int { - if (cpp2::testing.is_active() && !(arithmetic) ) { cpp2::testing.report_violation(""); } - if (cpp2::testing.is_active() && !(arithmetic) ) { cpp2::testing.report_violation(""); } +#line 19 "pure2-concept-definition.cpp2" +auto main() -> int{ + static_assert(arithmetic); + static_assert(arithmetic); + static_assert(number); + static_assert(number); + static_assert(number); + static_assert(!(number)); + static_assert(!(number>)); + static_assert(test_nonthrowing_requirements); + static_assert(!(test_nonthrowing_requirements)); } diff --git a/regression-tests/test-results/pure2-print.cpp b/regression-tests/test-results/pure2-print.cpp index 47725b573..f03d1f26d 100644 --- a/regression-tests/test-results/pure2-print.cpp +++ b/regression-tests/test-results/pure2-print.cpp @@ -55,30 +55,30 @@ CPP2_REQUIRES_ ((std::is_convertible_v && ...)) ; public: static auto test() -> void; -#line 92 "pure2-print.cpp2" +#line 105 "pure2-print.cpp2" public: template class x { private: std::tuple tup {}; public: x() = default; public: x(x const&) = delete; /* No 'that' constructor, suppress copy */ public: auto operator=(x const&) -> void = delete; -#line 94 "pure2-print.cpp2" +#line 107 "pure2-print.cpp2" }; public: template static auto print(std::ostream& out, Args const& ...args) -> void CPP2_REQUIRES_ (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) ; -#line 100 "pure2-print.cpp2" +#line 113 "pure2-print.cpp2" public: template [[nodiscard]] static auto all(Args const& ...args) -> bool; -#line 103 "pure2-print.cpp2" +#line 116 "pure2-print.cpp2" public: static auto y([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void; public: outer() = default; public: outer(outer const&) = delete; /* No 'that' constructor, suppress copy */ public: auto operator=(outer const&) -> void = delete; -#line 105 "pure2-print.cpp2" +#line 118 "pure2-print.cpp2" }; auto main() -> int; @@ -189,23 +189,36 @@ requires ((std::is_convertible_v && ...)) {(std::cout << .. if (cpp2::impl::is(_expr, 43)) { if constexpr( requires{"forty-and-three";} ) if constexpr( std::is_convertible_v ) return "forty-and-three"; else return namespace_alias::string{}; else return namespace_alias::string{}; } else return "default case"; } () << "\n"; + + static_cast( + requires {typename std::vector; +} + requires {0; +} + requires { + requires true; + requires !requires { typename std::vector; }; + requires !requires { 0; }; + { 0 }; + { 0 } noexcept; + { 0 } -> std::regular; + { 0 } noexcept -> std::regular; +}); } -#line 96 "pure2-print.cpp2" +#line 109 "pure2-print.cpp2" template auto outer::print(std::ostream& out, Args const& ...args) -> void requires (cpp2::impl::cmp_greater_eq(sizeof...(Args),0u)) { -#line 97 "pure2-print.cpp2" +#line 110 "pure2-print.cpp2" (out << ... << args); } -#line 100 "pure2-print.cpp2" +#line 113 "pure2-print.cpp2" template [[nodiscard]] auto outer::all(Args const& ...args) -> bool { return (... && args); } -#line 103 "pure2-print.cpp2" +#line 116 "pure2-print.cpp2" auto outer::y([[maybe_unused]] cpp2::impl::in unnamed_param_1) -> void{} -#line 107 "pure2-print.cpp2" +#line 120 "pure2-print.cpp2" auto main() -> int{ outer::test(); } diff --git a/regression-tests/test-results/pure2-print.cpp2.output b/regression-tests/test-results/pure2-print.cpp2.output index 2d4920924..cd3177e5b 100644 --- a/regression-tests/test-results/pure2-print.cpp2.output +++ b/regression-tests/test-results/pure2-print.cpp2.output @@ -131,6 +131,15 @@ outer:/* @print */ type = is 43 = "forty-and-three"; is _ = "default case"; } << "\n"; + _ = requires { std::vector; } + requires { _ = 0; } + requires { + requires true; + !requires std::vector; + !requires _ = 0; + { 0 }; + { 0 } !throws; + { 0 } is std::regular; + { 0 } !throws, is std::regular; + }; } x: type = { diff --git a/regression-tests/test-results/pure2-requires-clauses.cpp b/regression-tests/test-results/pure2-requires-clauses.cpp index a83ef305d..bccac486e 100644 --- a/regression-tests/test-results/pure2-requires-clauses.cpp +++ b/regression-tests/test-results/pure2-requires-clauses.cpp @@ -46,6 +46,10 @@ template CPP2_REQUIRES_ (std::same_as) extern T const v; #line 22 "pure2-requires-clauses.cpp2" +template auto g() -> void +CPP2_REQUIRES (true) ; + +#line 24 "pure2-requires-clauses.cpp2" auto main() -> int; //=== Cpp2 function definitions ================================================= @@ -75,6 +79,10 @@ template requires (std::same_as) T const v {0}; #line 22 "pure2-requires-clauses.cpp2" +template auto g() -> void +requires (true) {} + +#line 24 "pure2-requires-clauses.cpp2" auto main() -> int{ X auto_1 {}; std::cout << f(2, 5) diff --git a/source/parse.h b/source/parse.h index a8ad4bd9e..4cf584fd8 100644 --- a/source/parse.h +++ b/source/parse.h @@ -124,13 +124,14 @@ struct expression_list_node; struct id_expression_node; struct declaration_node; struct inspect_expression_node; +struct requires_expression_node; struct literal_node; struct template_argument; struct primary_expression_node { - enum active : u8 { empty=0, identifier, expression_list, id_expression, declaration, inspect, literal }; + enum active : u8 { empty=0, identifier, expression_list, id_expression, declaration, inspect, requires_, literal }; std::variant< std::monostate, token const*, @@ -138,6 +139,7 @@ struct primary_expression_node std::unique_ptr, std::unique_ptr, std::unique_ptr, + std::unique_ptr, std::unique_ptr > expr; // Cache to work around . @@ -217,7 +219,7 @@ struct literal_node { if ( !std::exchange(first, false) && p->as_string_view().starts_with("\"") - ) + ) { ret += " "; } @@ -2072,6 +2074,84 @@ struct inspect_expression_node }; +struct parameter_declaration_list_node; + +struct requires_expression_node +{ + struct compound_requirement_node { + std::unique_ptr expression; + bool throws = true; + std::unique_ptr type_constraint; + + auto position() const + -> source_position + { + assert(expression); + return expression->position(); + } + + auto visit(auto& v, int depth) + -> void + { + v.start(*this, depth); + assert (expression); + v.start(*expression, depth+1); + if (type_constraint) { + type_constraint->visit(v, depth+1); + } + v.end(*expression, depth+1); + v.end(*this, depth); + } + }; + + struct requirement_node { + enum active { type=0, expression, compound, nested, negative }; + std::variant< + std::unique_ptr, + std::unique_ptr, + std::unique_ptr, + std::unique_ptr, + std::unique_ptr + > requirement; + + auto position() const + -> source_position + { + return std::visit([](auto const& r) { + assert(r); + return r->position(); + }, requirement); + } + + auto visit(auto& v, int depth) + -> void + { + v.start(*this, depth); + try_visit(requirement, v, depth); + try_visit(requirement, v, depth); + try_visit(requirement, v, depth); + try_visit(requirement, v, depth); + try_visit(requirement, v, depth); + v.end(*this, depth); + } + }; + + token const* identifier = {}; + std::unique_ptr parameters; + std::vector> requirements; + + auto position() const + -> source_position + { + assert(identifier); + return identifier->position(); + } + + auto visit(auto& v, int depth) + -> void; +}; + + struct contract_node { // Declared first, because it should outlive any owned @@ -2423,13 +2503,15 @@ struct parameter_declaration_list_node bool in_function_typeid = false; bool in_template_param_list = false; bool in_statement_param_list = false; + bool in_requires_expression = false; std::vector> parameters; - parameter_declaration_list_node(bool f = false, bool t = false, bool s = false) + parameter_declaration_list_node(bool f = false, bool t = false, bool s = false, bool r = false) : in_function_typeid{f} , in_template_param_list{t} , in_statement_param_list{s} + , in_requires_expression{r} { } // API @@ -2509,6 +2591,23 @@ auto statement_node::visit(auto& v, int depth) } +auto requires_expression_node::visit(auto& v, int depth) + -> void +{ + v.start(*this, depth); + assert (identifier); + v.start(*identifier, depth+1); + if (parameters) { + parameters->visit(v, depth+1); + } + for (auto const& x : requirements) { + assert(x); + x->visit(v, depth+1); + } + v.end(*this, depth); +} + + struct function_returns_tag { }; struct function_type_node @@ -2542,7 +2641,7 @@ struct function_type_node assert (parameters); auto ret = parameters->to_string(); - + if (throws) { ret += " throws"; } @@ -4578,6 +4677,12 @@ auto primary_expression_node::position() const return i->position(); } + break;case requires_: { + auto const& i = std::get(expr); + assert (i); + return i->position(); + } + break;case literal: { auto const& i = std::get(expr); assert (i); @@ -4600,6 +4705,7 @@ auto primary_expression_node::visit(auto& v, int depth) try_visit(expr, v, depth); try_visit(expr, v, depth); try_visit(expr, v, depth); + try_visit(expr, v, depth); try_visit(expr, v, depth); v.end(*this, depth); } @@ -4788,6 +4894,11 @@ statement_node::~statement_node() = default; declaration_node::~declaration_node() = default; +struct type_requirement_tag {}; +struct simple_requirement_tag {}; +struct nested_requirement_tag {}; +struct negative_requirement_tag {}; + //----------------------------------------------------------------------- // // pretty_print_visualize: pretty-prints Cpp2 ASTs @@ -4838,6 +4949,20 @@ auto pretty_print_visualize(alternative_node const& n, int indent) -> std::string; auto pretty_print_visualize(inspect_expression_node const& n, int indent) -> std::string; +auto pretty_print_visualize(type_id_node const& n, type_requirement_tag, int indent) + -> std::string; +auto pretty_print_visualize(expression_node const& n, simple_requirement_tag, int indent) + -> std::string; +auto pretty_print_visualize(requires_expression_node::compound_requirement_node const& n, int indent) + -> std::string; +auto pretty_print_visualize(logical_or_expression_node const& n, nested_requirement_tag, int indent) + -> std::string; +auto pretty_print_visualize(requires_expression_node::requirement_node const& n, negative_requirement_tag, int indent) + -> std::string; +auto pretty_print_visualize(requires_expression_node::requirement_node const& n, int indent) + -> std::string; +auto pretty_print_visualize(requires_expression_node const& n, int indent) + -> std::string; auto pretty_print_visualize(contract_node const& n, int indent) -> std::string; auto pretty_print_visualize(jump_statement_node const& n, int indent) @@ -4973,6 +5098,7 @@ auto pretty_print_visualize(primary_expression_node const& n, int indent) ret += try_pretty_print_visualize(n.expr, indent); ret += try_pretty_print_visualize(n.expr, indent); ret += try_pretty_print_visualize(n.expr, indent); + ret += try_pretty_print_visualize(n.expr, indent); ret += try_pretty_print_visualize(n.expr, indent); return ret; @@ -5364,6 +5490,100 @@ auto pretty_print_visualize(inspect_expression_node const& n, int indent) } +auto pretty_print_visualize(type_id_node const& n, type_requirement_tag, int indent) + -> std::string +{ + return pretty_print_visualize(n, indent) + ";"; +} + +auto pretty_print_visualize(expression_node const& n, simple_requirement_tag, int indent) + -> std::string +{ + return "_ = " + pretty_print_visualize(n, indent) + ';'; +} + +auto pretty_print_visualize(requires_expression_node::compound_requirement_node const& n, int indent) + -> std::string +{ + auto ret = "{ " + pretty_print_visualize(*n.expression, indent) + " }"; + + if (!n.throws) { + ret += " !throws"; + if (n.type_constraint) { + ret += ","; + } + } + + if (n.type_constraint) { + ret += " is " + pretty_print_visualize(*n.type_constraint, indent); + } + + ret += ';'; + + return ret; +} + + +auto pretty_print_visualize(logical_or_expression_node const& n, nested_requirement_tag, int indent) + -> std::string +{ + return "requires " + pretty_print_visualize(n, indent) + ';'; +} + +auto pretty_print_visualize(requires_expression_node::requirement_node const& n, negative_requirement_tag, int indent) + -> std::string +{ + return "!requires " + pretty_print_visualize(n, indent); +} + +auto pretty_print_visualize(requires_expression_node::requirement_node const& n, int indent) + -> std::string +{ + auto ret = std::string{}; + + ret += try_pretty_print_visualize(n.requirement, type_requirement_tag{}, indent); + ret += try_pretty_print_visualize(n.requirement, simple_requirement_tag{}, indent); + ret += try_pretty_print_visualize(n.requirement, indent); + ret += try_pretty_print_visualize(n.requirement, nested_requirement_tag{}, indent); + ret += try_pretty_print_visualize(n.requirement, negative_requirement_tag{}, indent); + + return ret; +} + +auto pretty_print_visualize(requires_expression_node const& n, int indent) + -> std::string +{ + assert (n.identifier); + + auto ret = std::string{"requires"}; + + if (n.parameters) { + ret += pretty_print_visualize(*n.parameters, indent+1); + } + + ret += " {"; + + auto requirement_indent = pre(indent + 1); + char separator = '\n'; + if (n.requirements.size() <= 1) { + requirement_indent = {}; + separator = ' '; + } + + ret += separator; + + for (auto& req: n.requirements) { + assert(req); + ret += requirement_indent + pretty_print_visualize(*req, indent+2) + separator; + } + + ret += requirement_indent; + ret += '}'; + + return ret; +} + + auto pretty_print_visualize(contract_node const& n, int indent) -> std::string { @@ -5907,7 +6127,7 @@ class parser // // errors error list // - parser( + parser( std::vector& errors_, std::set& includes_ ) @@ -6145,6 +6365,7 @@ class parser //G primary-expression: //G inspect-expression + //G requires-expression //G id-expression //G literal //G '(' expression-list ','? ')' @@ -6161,6 +6382,12 @@ class parser return n; } + if (auto requires_ = requires_expression()) + { + n->expr = std::move(requires_); + return n; + } + if (auto id = id_expression()) { n->expr = std::move(id); return n; @@ -6370,9 +6597,9 @@ class parser // Next should be an expression-list followed by a ')' // If not, then this wasn't a call expression so backtrack to // the '(' which will be part of the next grammar production - is_inside_call_expr = true; + is_inside_call_expr = true; term.expr_list = expression_list(term.op, lexeme::RightParen); - is_inside_call_expr = false; + is_inside_call_expr = false; if ( term.expr_list @@ -6401,7 +6628,7 @@ class parser } } else if ( - ( + ( term.op->type() == lexeme::EllipsisLess || term.op->type() == lexeme::EllipsisEqual ) @@ -7321,7 +7548,7 @@ class parser n->ids.push_back( std::move(term) ); - for ( + for ( auto first_time_through_loop = true; curr().type() == lexeme::Scope; first_time_through_loop = false @@ -7340,7 +7567,7 @@ class parser && first_uid_was_std && term.scope_op->type() == lexeme::Scope && *term.id->identifier == "forward" - ) + ) { error("std::forward is not needed in Cpp2 - use 'forward' parameters/arguments instead", false); return {}; @@ -7944,6 +8171,210 @@ class parser } + //G requires-expression: + //G 'requires' parameter-declaration-list? requirement-body + //G + //G requirement-body: + //G '{' requirement-seq '}' + //G + //G requirement-seq: + //G requirement + //G requirement requirement-seq + //G + //G requirement: + //G nested-requirement + //G negative-requirement + //G simple-requirement + //G type-id ';' // Cpp1 type-requirement + //G compound-requirement + //G + //G simple-requirement: + //G '_' '=' expression ';' + //G + //G compound-requirement: + //G '{' expression '}' throws-specifier? ','? is-type-constraint? ';' + //G + //G nested-requirement: + //G 'requires' logical-or-expression ';' + //G + //G negative-requirement: + //G '!' 'requires' requirement + //G + auto requirement() + -> std::unique_ptr + { + auto as_requirement = [&](auto&& req) { + return std::make_unique(requires_expression_node::requirement_node{std::move(req)}); + }; + + auto curr_starts_negative_requirement = [&]() { + return curr().type() == lexeme::Not + && peek(1) + && *peek(1) == "requires"; + }; + + if (curr() == "requires") + { + next(); + if (curr_starts_negative_requirement()) { + error("a negative requirement is expressed without the leading 'requires' before '!'"); + } + auto e = logical_or_expression(); + if (!e) { + error("'requires' must be followed by an expression"); + return {}; + } + if (curr().type() != lexeme::Semicolon) + { + error("expected ';' at end of nested requirement"); + return {}; + } + next(); + return as_requirement(std::move(e)); + } + else if (curr_starts_negative_requirement()) + { + next(2); + auto r = requirement(); + if (!r) { + error("expected requirement"); + } + return as_requirement(std::move(r)); + } + else if ( + curr() == "_" + && peek(1) + && peek(1)->type() == lexeme::Assignment + ) + { + next(2); + auto e = expression(); + if (!e) + { + error("expected expression of simple requirement"); + return {}; + } + if (curr().type() != lexeme::Semicolon) + { + error("expected ';' at end of simple requirement"); + return {}; + } + next(); + return as_requirement(std::move(e)); + } + else if (auto t = type_id()) + { + if (curr().type() != lexeme::Semicolon) + { + error("expected ';' at end of type"); + return {}; + } + next(); + return as_requirement(std::move(t)); + } + else if (curr().type() == lexeme::LeftBrace) + { + next(); + auto r = std::make_unique(); + auto e = expression(); + if (!e) { + error("expected expression of compound requirement"); + return {}; + } + r->expression = std::move(e); + if (curr().type() != lexeme::RightBrace) + { + error("expected closing '}' of compound requirement"); + return {}; + } + next(); + if (curr().type() == lexeme::Not) + { + r->throws = false; + next(); + } + if (curr() == "throws") + { + if (r->throws) { + error("expected '!' before 'throws', or no 'throws' (the default)"); + return {}; + } + next(); + } + else if (!r->throws) + { + error("expected 'throws' after '!'"); + return {}; + } + auto has_comma = curr().type() == lexeme::Comma; + if (has_comma) + { + next(); + } + if (curr() == "is") + { + next(); + auto t = type_id(); + if (!t) { + error("expected type constraint"); + return {}; + } + r->type_constraint = std::move(t); + } + else if (has_comma) + { + error("expected type constraint after ','"); + return {}; + } + if (curr().type() != lexeme::Semicolon) + { + error("expected ';' at end of compound requirement"); + return {}; + } + next(); + return as_requirement(std::move(r)); + } + // else + return {}; + } + + auto requires_expression() + -> std::unique_ptr + { + if (curr() != "requires") { + return {}; + } + + auto n = std::make_unique(); + n->identifier = &curr(); + next(); + + if (auto p = parameter_declaration_list(false, true, false, false, false, true)) { + n->parameters = std::move(p); + } + + if (curr().type() != lexeme::LeftBrace) { + error("expected opening '{' of body of requires expression"); + return {}; + } + next(); + + while (curr().type() != lexeme::RightBrace) { + if (auto r = requirement()) { + n->requirements.push_back(std::move(r)); + } + else if (curr().type() != lexeme::RightBrace) + { + error("expected closing '}' of body of requires expression"); + return {}; + } + } + next(); + + return n; + } + + //G jump-statement: //G 'break' identifier? ';' //G 'continue' identifier? ';' @@ -7993,7 +8424,7 @@ class parser } if ( - peek(1) + peek(1) && *peek(1) == "namespace" ) { @@ -8451,7 +8882,8 @@ class parser bool is_named = true, bool is_template = false, bool is_statement = false, - bool is_function_typeid = false + bool is_function_typeid = false, + bool is_requires_expr = false ) -> std::unique_ptr { @@ -8472,7 +8904,7 @@ class parser return {}; } - auto n = std::make_unique(is_function_typeid, is_template, is_statement); + auto n = std::make_unique(is_function_typeid, is_template, is_statement, is_requires_expr); n->open_paren = &curr(); next(); @@ -8643,6 +9075,7 @@ class parser //G //G throws-specifier: //G 'throws' + //G '!' 'throws' //G //G return-list: //G expression-statement @@ -8700,7 +9133,9 @@ class parser ) { auto start_pos = pos; - auto at_an_expression = expression() != nullptr; + auto at_an_expression = + curr() != "requires" + && expression() != nullptr; pos = start_pos; // backtrack no matter what, we're just peeking here if (at_an_expression) { @@ -9974,6 +10409,11 @@ class parse_tree_printer : printing_visitor o << pre(indent+1) << "is_constexpr: " << _as(n.is_constexpr) << "\n"; } + auto start(requires_expression_node const&, int indent) -> void + { + o << pre(indent) << "requires-expression\n"; + } + auto start(return_statement_node const&, int indent) -> void { o << pre(indent) << "return-statement\n"; diff --git a/source/sema.h b/source/sema.h index f3d8ac63f..d564ded52 100644 --- a/source/sema.h +++ b/source/sema.h @@ -1130,7 +1130,7 @@ class sema // This warning is noisy until we fix a couple of bugs, // so disable it at least temporarily - // + // //// If we arrived back at the declaration without finding a use //// and this is a user-named object (not 'this', 'that', or '_') //if ( @@ -2253,7 +2253,7 @@ class sema // All the scope-local names stay active for lookup until the end of their scope while ( - !current_declarations.empty() + !current_declarations.empty() && current_declarations.back() != nullptr ) { @@ -2291,9 +2291,10 @@ class sema { inside_parameter_list.push_back( true ); if ( - !n.in_function_typeid + !n.in_function_typeid && !n.in_template_param_list - ) + && !n.in_requires_expression + ) { push_lifetime_scope(); } @@ -2615,7 +2616,7 @@ class sema // PARTIAL SUCCESS: Record the location of 'this' and keep going // found_this = *i; - prev_token_was_this = + prev_token_was_this = *prev2_token == "this" && (prev_token->type() == lexeme::Dot || prev_token->type() == lexeme::DotDot) ; diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 4a891ad1d..fb60ec5da 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -2186,6 +2186,97 @@ class cppfront } + //----------------------------------------------------------------------- + // + auto emit( + type_id_node const& n, + type_requirement_tag + ) + -> void + { + printer.print_cpp2("typename ", n.position()); + emit(n); + printer.print_cpp2(";", n.position()); + } + + auto emit( + expression_node const& n, + simple_requirement_tag + ) + -> void + { + emit(n); + printer.print_cpp2(";", n.position()); + } + + auto emit(requires_expression_node::compound_requirement_node const& n) + -> void + { + assert(n.expression); + printer.print_cpp2("{ ", n.expression->position()); + emit(*n.expression); + printer.print_cpp2(" }", n.expression->position()); + if (!n.throws) { + printer.print_cpp2(" noexcept", n.expression->position()); + } + if (n.type_constraint) + { + printer.print_cpp2(" -> ", n.expression->position()); + emit(*n.type_constraint); + } + printer.print_cpp2(";", n.expression->position()); + } + + auto emit( + logical_or_expression_node const& n, + nested_requirement_tag + ) + -> void + { + printer.print_cpp2("requires ", n.position()); + emit(n); + printer.print_cpp2(";", n.position()); + } + + auto emit( + requires_expression_node::requirement_node const& n, + negative_requirement_tag + ) + -> void + { + printer.print_cpp2("requires ", n.position()); + printer.print_cpp2("!requires { ", n.position()); + emit(n); + printer.print_cpp2(" };", n.position()); + } + + auto emit(requires_expression_node::requirement_node const& n) + -> void + { + try_emit(n.requirement, type_requirement_tag{}); + try_emit(n.requirement, simple_requirement_tag{}); + try_emit(n.requirement); + try_emit(n.requirement, nested_requirement_tag{}); + try_emit(n.requirement, negative_requirement_tag{}); + } + + auto emit(requires_expression_node const& n) + -> void + { + assert(n.identifier); + emit(*n.identifier); + if (n.parameters) { + emit(*n.parameters); + } + printer.print_cpp2(" {", n.position()); + for (auto const& x : n.requirements) { + assert(x); + emit(*x); + } + printer.print_cpp2("\n}", n.position(), true); + } + + //----------------------------------------------------------------------- // auto emit(selection_statement_node const& n) @@ -2730,6 +2821,7 @@ class cppfront try_emit(n.expr); try_emit(n.expr); try_emit(n.expr, true); + try_emit(n.expr); try_emit(n.expr); if (n.expr.index() == primary_expression_node::declaration)