From c119d32ea717320aa56a46e5e4a3a98d09ef4401 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 17 Dec 2023 12:22:18 -1000 Subject: [PATCH] Allow additional contract flags, e.g., `assert( ... )` --- regression-tests/pure2-contracts.cpp2 | 24 +++++++-- regression-tests/pure2-print.cpp2 | 4 +- .../clang-12/pure2-contracts.cpp.execution | 1 + .../gcc-10/pure2-contracts.cpp.execution | 1 + .../gcc-10/pure2-print.cpp.output | 24 ++++----- .../gcc-13/pure2-contracts.cpp.execution | 1 + .../msvc-2022/pure2-contracts.cpp.execution | 1 + .../test-results/pure2-contracts.cpp | 22 +++++++- regression-tests/test-results/pure2-print.cpp | 53 ++++++++++--------- .../test-results/pure2-print.cpp2.output | 2 +- regression-tests/test-results/version | 2 +- source/build.info | 2 +- source/parse.h | 39 +++++++++++--- source/to_cpp1.h | 15 +++++- 14 files changed, 136 insertions(+), 55 deletions(-) diff --git a/regression-tests/pure2-contracts.cpp2 b/regression-tests/pure2-contracts.cpp2 index 0b551243f..a42b5027c 100644 --- a/regression-tests/pure2-contracts.cpp2 +++ b/regression-tests/pure2-contracts.cpp2 @@ -1,13 +1,29 @@ test_condition_evaluation: (tag) -> bool = { std::cout << tag << "\n"; return true; } +audit: bool = true; + main: () = { + // A few basic tests assert( 1 != 2, "ack, arithmetic is buggy" ); assert( typeid(int) != typeid(double), "ack, C types are broken" ); assert( any-grammatical.kind(of, nonsense * here) is "all ignored" ); - assert( test_condition_evaluation(1), "default" ); // evaluated: prints "1" - assert( test_condition_evaluation(2), "type" ); // evaluated: prints "2" + // Now test that conditions are only evaluated if there's + // a handler active + any other control flags are enabled + + assert( test_condition_evaluation(1), "default" ); // evaluated: prints "1" + + // Type has a handler + assert( test_condition_evaluation(2), "type" ); // evaluated: prints "2" cpp2::Type.set_handler(); - assert( test_condition_evaluation(3), "type" ); // not evaluated - assert( test_condition_evaluation(4) ); // not evaluated + // Type does not have a handler + assert( test_condition_evaluation(3), "type" ); // not evaluated + + // Bounds has a handler, and audit is true + assert( test_condition_evaluation(4), "type" ); // evaluated: prints "4" + audit = false; + // Bounds has a handler, but audit is false + assert( test_condition_evaluation(5), "type" ); // not evaluated + + assert( test_condition_evaluation(6) ); // not evaluated } \ No newline at end of file diff --git a/regression-tests/pure2-print.cpp2 b/regression-tests/pure2-print.cpp2 index b56b42712..f475d70ff 100644 --- a/regression-tests/pure2-print.cpp2 +++ b/regression-tests/pure2-print.cpp2 @@ -1,6 +1,8 @@ // Exercise the pretty-print visualizer for the various grammar elements +testing_enabled: bool = false; + outer: @print type = { object_alias: T requires true == 42; @@ -30,7 +32,7 @@ outer: @print type = { private h: (s: std::string, inout m: std::map ) -> std::string pre( m.empty() == false || false, "message" ) - pre( 0 < m.ssize() < 100 && true != false ) + pre( 0 < m.ssize() < 100 && true != false ) = { a := :()={}; b := :()={}; diff --git a/regression-tests/test-results/clang-12/pure2-contracts.cpp.execution b/regression-tests/test-results/clang-12/pure2-contracts.cpp.execution index 1191247b6..e8a01cd98 100644 --- a/regression-tests/test-results/clang-12/pure2-contracts.cpp.execution +++ b/regression-tests/test-results/clang-12/pure2-contracts.cpp.execution @@ -1,2 +1,3 @@ 1 2 +4 diff --git a/regression-tests/test-results/gcc-10/pure2-contracts.cpp.execution b/regression-tests/test-results/gcc-10/pure2-contracts.cpp.execution index 1191247b6..e8a01cd98 100644 --- a/regression-tests/test-results/gcc-10/pure2-contracts.cpp.execution +++ b/regression-tests/test-results/gcc-10/pure2-contracts.cpp.execution @@ -1,2 +1,3 @@ 1 2 +4 diff --git a/regression-tests/test-results/gcc-10/pure2-print.cpp.output b/regression-tests/test-results/gcc-10/pure2-print.cpp.output index 21251be66..8910e3957 100644 --- a/regression-tests/test-results/gcc-10/pure2-print.cpp.output +++ b/regression-tests/test-results/gcc-10/pure2-print.cpp.output @@ -1,17 +1,17 @@ In file included from pure2-print.cpp:7: ../../../include/cpp2util.h:10005:33: error: expected unqualified-id before ‘static_assert’ -pure2-print.cpp2:7:1: note: in expansion of macro ‘CPP2_REQUIRES_’ -pure2-print.cpp2:65:59: error: expected ‘;’ at end of member declaration +pure2-print.cpp2:9:1: note: in expansion of macro ‘CPP2_REQUIRES_’ +pure2-print.cpp2:67:59: error: expected ‘;’ at end of member declaration In file included from pure2-print.cpp:7: ../../../include/cpp2util.h:10005:47: error: static assertion failed: GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10. -pure2-print.cpp2:66:1: note: in expansion of macro ‘CPP2_REQUIRES_’ +pure2-print.cpp2:68:1: note: in expansion of macro ‘CPP2_REQUIRES_’ ../../../include/cpp2util.h:10005:33: error: expected initializer before ‘static_assert’ -pure2-print.cpp2:94:1: note: in expansion of macro ‘CPP2_REQUIRES_’ -pure2-print.cpp2:7:41: error: ‘constexpr const T outer::object_alias’ is not a static data member of ‘class outer’ -pure2-print.cpp2:7:48: error: template definition of non-template ‘constexpr const T outer::object_alias’ -pure2-print.cpp2:65:14: error: no declaration matches ‘void outer::mytype::variadic(const auto:90& ...) requires (is_convertible_v::type>::type, int> && ...)’ -pure2-print.cpp2:65:29: note: candidate is: ‘template static void outer::mytype::variadic(const auto:89& ...)’ -pure2-print.cpp2:8:19: note: ‘class outer::mytype’ defined here -pure2-print.cpp2:93:37: error: no declaration matches ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’ -pure2-print.cpp2:93:37: note: no functions named ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’ -pure2-print.cpp2:4:7: note: ‘class outer’ defined here +pure2-print.cpp2:96:1: note: in expansion of macro ‘CPP2_REQUIRES_’ +pure2-print.cpp2:9:41: error: ‘constexpr const T outer::object_alias’ is not a static data member of ‘class outer’ +pure2-print.cpp2:9:48: error: template definition of non-template ‘constexpr const T outer::object_alias’ +pure2-print.cpp2:67:14: error: no declaration matches ‘void outer::mytype::variadic(const auto:90& ...) requires (is_convertible_v::type>::type, int> && ...)’ +pure2-print.cpp2:67:29: note: candidate is: ‘template static void outer::mytype::variadic(const auto:89& ...)’ +pure2-print.cpp2:10:19: note: ‘class outer::mytype’ defined here +pure2-print.cpp2:95:37: error: no declaration matches ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’ +pure2-print.cpp2:95:37: note: no functions named ‘void outer::print(std::ostream&, const Args& ...) requires cpp2::cmp_greater_eq(sizeof (Args)..., 0)’ +pure2-print.cpp2:6:7: note: ‘class outer’ defined here diff --git a/regression-tests/test-results/gcc-13/pure2-contracts.cpp.execution b/regression-tests/test-results/gcc-13/pure2-contracts.cpp.execution index 1191247b6..e8a01cd98 100644 --- a/regression-tests/test-results/gcc-13/pure2-contracts.cpp.execution +++ b/regression-tests/test-results/gcc-13/pure2-contracts.cpp.execution @@ -1,2 +1,3 @@ 1 2 +4 diff --git a/regression-tests/test-results/msvc-2022/pure2-contracts.cpp.execution b/regression-tests/test-results/msvc-2022/pure2-contracts.cpp.execution index 1191247b6..e8a01cd98 100644 --- a/regression-tests/test-results/msvc-2022/pure2-contracts.cpp.execution +++ b/regression-tests/test-results/msvc-2022/pure2-contracts.cpp.execution @@ -1,2 +1,3 @@ 1 2 +4 diff --git a/regression-tests/test-results/pure2-contracts.cpp b/regression-tests/test-results/pure2-contracts.cpp index 90e828fee..17ddf6f59 100644 --- a/regression-tests/test-results/pure2-contracts.cpp +++ b/regression-tests/test-results/pure2-contracts.cpp @@ -15,6 +15,8 @@ [[nodiscard]] auto test_condition_evaluation(auto const& tag) -> bool; #line 3 "pure2-contracts.cpp2" +extern bool audit; + auto main() -> int; //=== Cpp2 function definitions ================================================= @@ -23,15 +25,31 @@ auto main() -> int; [[nodiscard]] auto test_condition_evaluation(auto const& tag) -> bool{std::cout << tag << "\n"; return true; } #line 3 "pure2-contracts.cpp2" +bool audit {true}; + auto main() -> int{ + // A few basic tests if (cpp2::Default.has_handler() && !(1 != 2) ) { cpp2::Default.violation("ack, arithmetic is buggy"); } if (cpp2::Type.has_handler() && !(typeid(int) != typeid(double)) ) { cpp2::Type.violation("ack, C types are broken"); } -#line 8 "pure2-contracts.cpp2" +#line 11 "pure2-contracts.cpp2" + // Now test that conditions are only evaluated if there's + // a handler active + any other control flags are enabled + if (cpp2::Default.has_handler() && !(test_condition_evaluation(1)) ) { cpp2::Default.violation("default"); }// evaluated: prints "1" + + // Type has a handler if (cpp2::Type.has_handler() && !(test_condition_evaluation(2)) ) { cpp2::Type.violation("type"); }// evaluated: prints "2" CPP2_UFCS(set_handler)(cpp2::Type); + // Type does not have a handler if (cpp2::Type.has_handler() && !(test_condition_evaluation(3)) ) { cpp2::Type.violation("type"); }// not evaluated - // not evaluated + + // Bounds has a handler, and audit is true + if (cpp2::Bounds.has_handler() && audit && !(test_condition_evaluation(4)) ) { cpp2::Bounds.violation("type"); }// evaluated: prints "4" + audit = false; + // Bounds has a handler, but audit is false + if (cpp2::Bounds.has_handler() && audit && !(test_condition_evaluation(5)) ) { cpp2::Bounds.violation("type"); }// not evaluated + + // not evaluated } diff --git a/regression-tests/test-results/pure2-print.cpp b/regression-tests/test-results/pure2-print.cpp index 8983a9435..393ccbe6d 100644 --- a/regression-tests/test-results/pure2-print.cpp +++ b/regression-tests/test-results/pure2-print.cpp @@ -8,7 +8,7 @@ #line 1 "pure2-print.cpp2" -#line 4 "pure2-print.cpp2" +#line 6 "pure2-print.cpp2" class outer; @@ -19,9 +19,11 @@ class outer; // Exercise the pretty-print visualizer for the various grammar elements #line 4 "pure2-print.cpp2" +extern bool testing_enabled; + class outer { -#line 6 "pure2-print.cpp2" +#line 8 "pure2-print.cpp2" public: template CPP2_REQUIRES_ (true) static const T object_alias; public: class mytype final @@ -30,16 +32,16 @@ CPP2_REQUIRES_ (true) static const T object_alias; public: [[nodiscard]] virtual auto g(cpp2::in i) const -> int; -#line 31 "pure2-print.cpp2" +#line 33 "pure2-print.cpp2" private: [[nodiscard]] static auto h(cpp2::in s, std::map& m) -> std::string; struct values_ret { int offset; std::string name; }; -#line 54 "pure2-print.cpp2" +#line 56 "pure2-print.cpp2" public: template [[nodiscard]] auto values([[maybe_unused]] T const& unnamed_param_2) const& -> values_ret; -#line 59 "pure2-print.cpp2" +#line 61 "pure2-print.cpp2" public: explicit mytype(); public: mytype([[maybe_unused]] mytype const& that); @@ -48,32 +50,32 @@ struct values_ret { int offset; std::string name; }; public: static auto variadic(auto const& ...x) -> void CPP2_REQUIRES_ ((std::is_convertible_v && ...)) ; -#line 66 "pure2-print.cpp2" +#line 68 "pure2-print.cpp2" }; public: static auto test() -> void; -#line 89 "pure2-print.cpp2" +#line 91 "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 91 "pure2-print.cpp2" +#line 93 "pure2-print.cpp2" }; public: template static auto print(std::ostream& out, Args const& ...args) -> void CPP2_REQUIRES_ (cpp2::cmp_greater_eq(sizeof(Args)...,0)) ; -#line 97 "pure2-print.cpp2" +#line 99 "pure2-print.cpp2" public: template [[nodiscard]] static auto all(Args const& ...args) -> bool; public: outer() = default; public: outer(outer const&) = delete; /* No 'that' constructor, suppress copy */ public: auto operator=(outer const&) -> void = delete; -#line 100 "pure2-print.cpp2" +#line 102 "pure2-print.cpp2" }; auto main() -> int; @@ -82,12 +84,15 @@ auto main() -> int; #line 1 "pure2-print.cpp2" -#line 6 "pure2-print.cpp2" +#line 4 "pure2-print.cpp2" +bool testing_enabled {false}; + +#line 8 "pure2-print.cpp2" template requires (true) inline CPP2_CONSTEXPR T outer::object_alias = 42; -#line 7 "pure2-print.cpp2" +#line 9 "pure2-print.cpp2" -#line 10 "pure2-print.cpp2" +#line 12 "pure2-print.cpp2" [[nodiscard]] auto outer::mytype::f() -> int { return 42; } [[nodiscard]] auto outer::mytype::g(cpp2::in i) const -> int{ @@ -111,11 +116,11 @@ requires (true) inline CPP2_CONSTEXPR T outer::object_alias = 42; [[nodiscard]] auto outer::mytype::h(cpp2::in s, std::map& m) -> std::string -#line 34 "pure2-print.cpp2" +#line 36 "pure2-print.cpp2" { if (cpp2::Default.has_handler() && !(CPP2_UFCS(empty)(m) == false || false) ) { cpp2::Default.violation("message"); } - if (cpp2::Bounds.has_handler() && !([_0 = 0, _1 = CPP2_UFCS(ssize)(m), _2 = 100]{ return cpp2::cmp_less(_0,_1) && cpp2::cmp_less(_1,_2); }() && true != false) ) { cpp2::Bounds.violation(""); } -#line 35 "pure2-print.cpp2" + if (cpp2::Bounds.has_handler() && testing_enabled && !([_0 = 0, _1 = CPP2_UFCS(ssize)(m), _2 = 100]{ return cpp2::cmp_less(_0,_1) && cpp2::cmp_less(_1,_2); }() && true != false) ) { cpp2::Bounds.violation(""); } +#line 37 "pure2-print.cpp2" auto a {[]() mutable -> void{}}; auto b {[]() mutable -> void{}}; auto c {[]() mutable -> void{}}; @@ -124,9 +129,9 @@ requires (true) inline CPP2_CONSTEXPR T outer::object_alias = 42; do {} while ( [&]{ b() ; return true; }() && CPP2_UFCS(empty)(s)); - for ( [[maybe_unused]] auto const& unnamed_param_1 : m ) { { do {goto CONTINUE_43_13; } while (false); c(); } CPP2_CONTINUE_BREAK(43_13) } + for ( [[maybe_unused]] auto const& unnamed_param_1 : m ) { { do {goto CONTINUE_45_13; } while (false); c(); } CPP2_CONTINUE_BREAK(45_13) } -#line 45 "pure2-print.cpp2" +#line 47 "pure2-print.cpp2" if (cpp2::is(!(CPP2_UFCS(empty)(s)), (true))) {std::move(a)(); } else {if (!(CPP2_UFCS(empty)(m))) {std::move(b)(); } else {std::move(c)(); }} @@ -139,7 +144,7 @@ requires (true) inline CPP2_CONSTEXPR T outer::object_alias = 42; template [[nodiscard]] auto outer::mytype::values([[maybe_unused]] T const& unnamed_param_2) const& -> values_ret{ cpp2::deferred_init offset; cpp2::deferred_init name; -#line 55 "pure2-print.cpp2" +#line 57 "pure2-print.cpp2" offset.construct(53); name.construct("plugh"); return { std::move(offset.value()), std::move(name.value()) }; } @@ -153,7 +158,7 @@ requires (true) inline CPP2_CONSTEXPR T outer::object_alias = 42; auto outer::mytype::variadic(auto const& ...x) -> void requires ((std::is_convertible_v && ...)) {(std::cout << ... << x); } -#line 68 "pure2-print.cpp2" +#line 70 "pure2-print.cpp2" auto outer::test() -> void{ namespace namespace_alias = ::std; @@ -165,7 +170,7 @@ requires ((std::is_convertible_v && ...)) {(std::cout << .. cpp2::i8 constexpr object_alias_1 = 42; auto constexpr object_alias_2 = 42; -#line 80 "pure2-print.cpp2" +#line 82 "pure2-print.cpp2" ::outer::mytype var {}; cout << CPP2_UFCS(g)(var, 42) << "\n"; @@ -175,17 +180,17 @@ requires ((std::is_convertible_v && ...)) {(std::cout << .. () << "\n"; } -#line 93 "pure2-print.cpp2" +#line 95 "pure2-print.cpp2" template auto outer::print(std::ostream& out, Args const& ...args) -> void requires (cpp2::cmp_greater_eq(sizeof(Args)...,0)) { -#line 94 "pure2-print.cpp2" +#line 96 "pure2-print.cpp2" (out << ... << args); } template [[nodiscard]] auto outer::all(Args const& ...args) -> bool { return (... && args); } -#line 102 "pure2-print.cpp2" +#line 104 "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 17f642bbe..5d493451d 100644 --- a/regression-tests/test-results/pure2-print.cpp2.output +++ b/regression-tests/test-results/pure2-print.cpp2.output @@ -33,7 +33,7 @@ outer: type = inout m: std::map ) -> move std::string pre( m.empty() == false || false, "message" ) - pre( 0 < m.ssize() < 100 && true != false ) = + pre( 0 < m.ssize() < 100 && true != false ) = { a: = :() = { diff --git a/regression-tests/test-results/version b/regression-tests/test-results/version index fded15a42..8f43886db 100644 --- a/regression-tests/test-results/version +++ b/regression-tests/test-results/version @@ -1,5 +1,5 @@ -cppfront compiler v0.3.0 Build 8C17:0658 +cppfront compiler v0.3.0 Build 8C17:1133 Copyright(c) Herb Sutter All rights reserved SPDX-License-Identifier: CC-BY-NC-ND-4.0 diff --git a/source/build.info b/source/build.info index 181826b17..fbbc590ac 100644 --- a/source/build.info +++ b/source/build.info @@ -1 +1 @@ -"8C17:0658" \ No newline at end of file +"8C17:1133" \ No newline at end of file diff --git a/source/parse.h b/source/parse.h index bc320d39c..8372abd50 100644 --- a/source/parse.h +++ b/source/parse.h @@ -1801,11 +1801,12 @@ struct contract_node // postfix_expressions that could refer to it capture_group captures; - source_position open_bracket; - token const* kind = {}; - std::unique_ptr group; - std::unique_ptr condition; - token const* message = {}; + source_position open_bracket; + token const* kind = {}; + std::unique_ptr group; + std::vector> flags; + std::unique_ptr condition; + token const* message = {}; contract_node( source_position pos ) : open_bracket{pos} @@ -1829,6 +1830,10 @@ struct contract_node group->visit(v, depth+1); } + for (auto const& f : flags) { + f->visit(v, depth+1); + } + assert(condition); condition->visit(v, depth+1); @@ -4735,7 +4740,11 @@ auto pretty_print_visualize(contract_node const& n, int indent) auto ret = std::string{"\n"} + pre(indent) + n.kind->as_string_view(); if (n.group) { - ret += "<" + pretty_print_visualize(*n.group, indent) + ">"; + ret += "<" + pretty_print_visualize(*n.group, indent); + for (auto const& flag : n.flags) { + ret += "," + pretty_print_visualize(*flag, indent); + } + ret += ">"; } ret += "( " + pretty_print_visualize(*n.condition, indent); @@ -7705,7 +7714,10 @@ class parser //G contract-kind contract-group? ':' '(' logical-or-expression ',' string-literal ')' //G //G contract-group: - //G '<' id-expression '>' + //G '<' id-expression contract-flags?'>' + //G + //G contract-flags: + //G ',' id-expression contract-flags? //G //G contract-kind: one of //G 'pre' 'post' 'assert' @@ -7727,6 +7739,7 @@ class parser n->kind = &curr(); next(); + // Check if there's a if (curr().type() == lexeme::Less) { next(); if (auto id = id_expression()) { @@ -7737,6 +7750,18 @@ class parser return {}; } + // Now check if there's a list of flags + while (curr().type() == lexeme::Comma) { + next(); + if (auto id = id_expression()) { + n->flags.push_back( std::move(id) ); + } + else { + error("invalid contract tag in list"); + return {}; + } + } + if (curr().type() != lexeme::Greater) { error("expected '>' after contract group"); return {}; diff --git a/source/to_cpp1.h b/source/to_cpp1.h index 400f27fca..b302e8d73 100644 --- a/source/to_cpp1.h +++ b/source/to_cpp1.h @@ -4577,8 +4577,19 @@ class cppfront } printer.print_cpp2( - "if (" + name + ".has_handler() && !(" + print_to_string(*n.condition) + ") ) " + - "{ " + name + ".violation(" + message + "); }", n.position() + "if (" + name + ".has_handler()", + n.position() + ); + for (auto const& flag : n.flags) { + printer.print_cpp2( + " && " + print_to_string(*flag), + n.position() + ); + } + printer.print_cpp2( + " && !(" + print_to_string(*n.condition) + ") ) " + + "{ " + name + ".violation(" + message + "); }", + n.position() ); // For a postcondition, close out the lambda