diff --git a/regression-tests/pure2-nested-ifs-conditions-error.cpp2 b/regression-tests/pure2-nested-ifs-conditions-error.cpp2 new file mode 100644 index 0000000000..665aa3181e --- /dev/null +++ b/regression-tests/pure2-nested-ifs-conditions-error.cpp2 @@ -0,0 +1,165 @@ +main: (args) = +{ + a := 1; + b := 2; + c := 3; + d := 4; + + { + p : *int; + + // no initialization in selection_node + if args.argc > 20 { + c = 20; + } else if args.argc > 10 { + c = 10; + } else { + if args.argc % 2 { + b = 2; + } else { + b = 1; + } + } + + // initialization in first if condition + if p = b& { + p = a&; + } else if args.argc == 3 { + p = b&; + if args.argc == 2 { + p = c&; + } else { + if b > 0 { + p = a&; + } + else { + p = d&; + } + } + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + + { + p : *int; + + // initialization in: + // - first if branch + // - second if condition + if args.argc == 3 { + p = a&; + } else if p = b& { + p = b&; + if args.argc == 2 { + p = c&; + } else { + if b > 0 { + p = a&; + } + else { + p = d&; + } + } + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + + { + p : *int; + + // initialization in: + // - first if branch + // - second if branch in nested if condition (first if) + // - else branch + if args.argc == 3 { + p = a&; + } else if args.argc == 2 { + if p = b& { + p = c&; + } else { + if b > 0 { + p = a&; + } + else { + p = d&; + } + } + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + + { + p : *int; + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if condition (first if) + // - else branch + if args.argc == 3 { + p = a&; + } else if args.argc == 2 { + if b > 0 { + p = c&; + } else { + if p = b& { + p = a&; + } + else { + p = d&; + } + p* = 42; + } + p* = 24; + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + + { + p : *int; + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if: + // - first if branch + // - second if condition + // - else branch + if args.argc == 3 { + p = a&; + } else if args.argc == 2 { + if b > 0 { + p = c&; + } else { + if b > 2 { + p = a&; + } + else if p = b& { + d = p*; + } + else { + p = d&; + } + } + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + +} diff --git a/regression-tests/pure2-nested-ifs-error.cpp2 b/regression-tests/pure2-nested-ifs-error.cpp2 new file mode 100644 index 0000000000..8ff11f1f08 --- /dev/null +++ b/regression-tests/pure2-nested-ifs-error.cpp2 @@ -0,0 +1,27 @@ +main: (args) = { + p : *int; + + a := 1; + b := 2; + c := 3; + d := 4; + + if args.argc == 3 { + p = a&; + } else if b > 2 { + if args.argc == 2 { + p = c&; + } else { + if b > 0 { + p = a&; + } + else { + p = d&; + } + } + } else { + // p = c&; + } + + std::cout << p* << std::endl; +} \ No newline at end of file diff --git a/regression-tests/pure2-nested-ifs.cpp2 b/regression-tests/pure2-nested-ifs.cpp2 new file mode 100644 index 0000000000..535f8a5a3e --- /dev/null +++ b/regression-tests/pure2-nested-ifs.cpp2 @@ -0,0 +1,52 @@ +main: (args) = +{ + a := 1; + b := 2; + c := 3; + d := 4; + + { + p : *int; + + // no initialization in selection_node + if args.argc > 20 { + c = 20; + } else if args.argc > 10 { + c = 10; + } else { + if args.argc % 2 { + b = 2; + } else { + b = 1; + } + } + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if: + // - first if branch + // - else branch + // - else branch + if args.argc == 3 { + p = a&; + } else if args.argc == 2 { + if b > 0 { + p = c&; + } else { + if b > 2 { + p = a&; + } + else { + p = d&; + } + } + } else { + p = c&; + } + + std::cout << p* << std::endl; + } + +} diff --git a/regression-tests/test-results/apple-clang-14/pure2-nested-ifs.cpp.execution b/regression-tests/test-results/apple-clang-14/pure2-nested-ifs.cpp.execution new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/regression-tests/test-results/apple-clang-14/pure2-nested-ifs.cpp.execution @@ -0,0 +1 @@ +3 diff --git a/regression-tests/test-results/apple-clang-14/pure2-nested-ifs.cpp.output b/regression-tests/test-results/apple-clang-14/pure2-nested-ifs.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/clang-12/pure2-nested-ifs.cpp.execution b/regression-tests/test-results/clang-12/pure2-nested-ifs.cpp.execution new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/regression-tests/test-results/clang-12/pure2-nested-ifs.cpp.execution @@ -0,0 +1 @@ +3 diff --git a/regression-tests/test-results/clang-12/pure2-nested-ifs.cpp.output b/regression-tests/test-results/clang-12/pure2-nested-ifs.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/gcc-10/pure2-nested-ifs.cpp.execution b/regression-tests/test-results/gcc-10/pure2-nested-ifs.cpp.execution new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/regression-tests/test-results/gcc-10/pure2-nested-ifs.cpp.execution @@ -0,0 +1 @@ +3 diff --git a/regression-tests/test-results/gcc-10/pure2-nested-ifs.cpp.output b/regression-tests/test-results/gcc-10/pure2-nested-ifs.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/msvc-2022/pure2-nested-ifs.cpp.execution b/regression-tests/test-results/msvc-2022/pure2-nested-ifs.cpp.execution new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/regression-tests/test-results/msvc-2022/pure2-nested-ifs.cpp.execution @@ -0,0 +1 @@ +3 diff --git a/regression-tests/test-results/msvc-2022/pure2-nested-ifs.cpp.output b/regression-tests/test-results/msvc-2022/pure2-nested-ifs.cpp.output new file mode 100644 index 0000000000..e69de29bb2 diff --git a/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp b/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp new file mode 100644 index 0000000000..62f9c029d3 --- /dev/null +++ b/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp @@ -0,0 +1,187 @@ + +#define CPP2_USE_MODULES Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-nested-ifs-conditions-error.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-nested-ifs-conditions-error.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int +{ + auto args = cpp2::make_args(argc_, argv_); +#line 3 "pure2-nested-ifs-conditions-error.cpp2" + auto a {1}; + auto b {2}; + auto c {3}; + auto d {4}; + + { + cpp2::deferred_init p; + + // no initialization in selection_node + if (cpp2::cmp_greater(args.argc,20)) { + c = 20; + } else if (cpp2::cmp_greater(args.argc,10)) { + c = 10; + }else { + if (args.argc % 2) { + b = 2; + }else { + b = 1; + } + } + + // initialization in first if condition + if (p.construct(&b)) { + p.value() = &a; + } else if (args.argc==3) { + p.value() = &b; + if (args.argc==2) { + p.value() = &c; + }else { + if (cpp2::cmp_greater(b,0)) { + p.value() = &a; + } + else { + p.value() = &d; + } + } + }else { + p.value() = &c; + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + + { + cpp2::deferred_init p; + + // initialization in: + // - first if branch + // - second if condition + if (args.argc==3) { + p.construct(&a); + } else if (p.construct(&b)) { + p.value() = &b; + if (args.argc==2) { + p.value() = &c; + }else { + if (cpp2::cmp_greater(b,0)) { + p.value() = &a; + } + else { + p.value() = &d; + } + } + }else { + p.value() = &c; + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + + { + cpp2::deferred_init p; + + // initialization in: + // - first if branch + // - second if branch in nested if condition (first if) + // - else branch + if (args.argc==3) { + p.construct(&a); + } else if (args.argc==2) { + if (p.construct(&b)) { + p.value() = &c; + }else { + if (cpp2::cmp_greater(b,0)) { + p.value() = &a; + } + else { + p.value() = &d; + } + } + }else { + p.construct(&c); + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + + { + cpp2::deferred_init p; + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if condition (first if) + // - else branch + if (args.argc==3) { + p.construct(&a); + } else if (args.argc==2) { + if (cpp2::cmp_greater(b,0)) { + p.construct(&c); + }else { + if (p.construct(&b)) { + p.value() = &a; + } + else { + p.value() = &d; + } + *cpp2::assert_not_null(p.value()) = 42; + } + *cpp2::assert_not_null(p.value()) = 24; + }else { + p.construct(&c); + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + + { + cpp2::deferred_init p; + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if: + // - first if branch + // - second if condition + // - else branch + if (args.argc==3) { + p.construct(&a); + } else if (args.argc==2) { + if (cpp2::cmp_greater(b,0)) { + p.construct(&c); + }else { + if (cpp2::cmp_greater(b,2)) { + p.construct(&a); + } + else if (p.construct(&b)) { + d = *cpp2::assert_not_null(p.value()); + } + else { + p.value() = &d; + } + } + }else { + p.construct(&c); + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + +} + diff --git a/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp2.output b/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp2.output new file mode 100644 index 0000000000..bffb6b27ce --- /dev/null +++ b/regression-tests/test-results/pure2-nested-ifs-conditions-error.cpp2.output @@ -0,0 +1,2 @@ +pure2-nested-ifs-conditions-error.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/regression-tests/test-results/pure2-nested-ifs-error.cpp2.output b/regression-tests/test-results/pure2-nested-ifs-error.cpp2.output new file mode 100644 index 0000000000..9e33ea748d --- /dev/null +++ b/regression-tests/test-results/pure2-nested-ifs-error.cpp2.output @@ -0,0 +1,9 @@ +pure2-nested-ifs-error.cpp2... +pure2-nested-ifs-error.cpp2(2,5): error: local variable p must be initialized on both branches or neither branch +pure2-nested-ifs-error.cpp2(9,5): error: "if" initializes p on: + branch starting at line 9 + branch starting at line 11 +but not on: + branch starting at line 22 + ==> program violates initialization safety guarantee - see previous errors + diff --git a/regression-tests/test-results/pure2-nested-ifs.cpp b/regression-tests/test-results/pure2-nested-ifs.cpp new file mode 100644 index 0000000000..f701f0dca3 --- /dev/null +++ b/regression-tests/test-results/pure2-nested-ifs.cpp @@ -0,0 +1,74 @@ + +#define CPP2_USE_MODULES Yes + +//=== Cpp2 type declarations ==================================================== + + +#include "cpp2util.h" + + + +//=== Cpp2 type definitions and function declarations =========================== + +#line 1 "pure2-nested-ifs.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int; + + +//=== Cpp2 function definitions ================================================= + +#line 1 "pure2-nested-ifs.cpp2" +auto main(int const argc_, char const* const* const argv_) -> int +{ + auto args = cpp2::make_args(argc_, argv_); +#line 3 "pure2-nested-ifs.cpp2" + auto a {1}; + auto b {2}; + auto c {3}; + auto d {4}; + + { + cpp2::deferred_init p; + + // no initialization in selection_node + if (cpp2::cmp_greater(args.argc,20)) { + c = 20; + } else if (cpp2::cmp_greater(args.argc,10)) { + c = 10; + }else { + if (args.argc % 2) { + b = 2; + }else { + b = 1; + } + } + + // initialization in: + // - first if branch + // - second if branch in nested if: + // - first branch + // - second branch in nested if: + // - first if branch + // - else branch + // - else branch + if (args.argc==3) { + p.construct(&a); + } else if (args.argc==2) { + if (cpp2::cmp_greater(b,0)) { + p.construct(&c); + }else { + if (cpp2::cmp_greater(std::move(b),2)) { + p.construct(&a); + } + else { + p.construct(&d); + } + } + }else { + p.construct(&c); + } + + std::cout << *cpp2::assert_not_null(std::move(p.value())) << std::endl; + } + +} + diff --git a/regression-tests/test-results/pure2-nested-ifs.cpp2.output b/regression-tests/test-results/pure2-nested-ifs.cpp2.output new file mode 100644 index 0000000000..2ac9b570c8 --- /dev/null +++ b/regression-tests/test-results/pure2-nested-ifs.cpp2.output @@ -0,0 +1,2 @@ +pure2-nested-ifs.cpp2... ok (all Cpp2, passes safety checks) + diff --git a/source/cppfront.cpp b/source/cppfront.cpp index b8ded91a2f..41e6e7abb0 100644 --- a/source/cppfront.cpp +++ b/source/cppfront.cpp @@ -2017,6 +2017,26 @@ class cppfront assert(n.true_branch); emit(*n.true_branch); + for(const auto& elif : n.else_ifs) { + printer.print_cpp2(" else if ", elif.pos); + + if (elif.is_constexpr) { + printer.print_cpp2("constexpr ", elif.pos); + } + + printer.print_cpp2("(", elif.pos); + printer.add_pad_in_this_line(1); + + assert(elif.expression); + emit(*elif.expression); + + printer.print_cpp2(") ", elif.pos); + printer.add_pad_in_this_line(1); + + assert(elif.branch); + emit(*elif.branch); + } + if (n.has_source_false_branch) { printer.print_cpp2("else ", n.else_pos); emit(*n.false_branch); diff --git a/source/parse.h b/source/parse.h index 8b4af5a0d5..00a8a644c3 100644 --- a/source/parse.h +++ b/source/parse.h @@ -1168,6 +1168,14 @@ struct selection_statement_node std::unique_ptr false_branch; bool has_source_false_branch = false; + struct else_if { + bool is_constexpr = false; + std::unique_ptr expression; + std::unique_ptr branch; + source_position pos; + }; + std::vector else_ifs; + auto position() const -> source_position { @@ -1185,6 +1193,14 @@ struct selection_statement_node expression->visit(v, depth+1); assert (true_branch); true_branch->visit(v, depth+1); + + for(const auto& elif : else_ifs) { + assert (elif.expression); + elif.expression->visit(v, depth+1); + assert (elif.branch); + elif.branch->visit(v, depth+1); + } + if (false_branch) { false_branch->visit(v, depth+1); } @@ -4643,6 +4659,48 @@ class parser return {}; } + while ( + curr().type() == lexeme::Keyword + && curr() == "else" + && peek(1) && peek(1)->type() == lexeme::Keyword && peek(1)->to_string(true) == "if" + ) { + selection_statement_node::else_if elif; + elif.pos = curr().position(); + next(2); // skip "else if" + + if ( + curr().type() == lexeme::Keyword + && curr() == "constexpr" + ) + { + elif.is_constexpr = true; + next(); + } + + if (auto e = expression()) { + elif.expression = std::move(e); + } + else { + error("invalid else if condition", true, {}, true); + return {}; + } + + if (curr().type() != lexeme::LeftBrace) { + error("an if branch body must be enclosed with { }"); + return {}; + } + + if (auto s = compound_statement()) { + elif.branch = std::move(s); + } + else { + error("invalid if branch body", true, {}, true); + return {}; + } + + n->else_ifs.emplace_back(std::move(elif)); + } + if ( curr().type() != lexeme::Keyword || curr() != "else" diff --git a/source/sema.h b/source/sema.h index 8a66d38cd6..2f5dc10f6c 100644 --- a/source/sema.h +++ b/source/sema.h @@ -104,7 +104,7 @@ struct selection_sym { struct compound_sym { bool start = false; compound_statement_node const* compound = {}; - enum kind { is_scope, is_true, is_false } kind_ = is_scope; + enum kind { is_scope, is_if, is_else_if, is_else } kind_ = is_scope; compound_sym( bool s, @@ -434,11 +434,14 @@ class sema o << "/"; --scope_depth; } - if (sym.kind_ == sym.is_true) { - o << "true branch"; + if (sym.kind_ == sym.is_if) { + o << "if branch"; } - else if (sym.kind_ == sym.is_false) { - o << "false branch"; + else if (sym.kind_ == sym.is_else_if) { + o << "if else branch"; + } + else if (sym.kind_ == sym.is_else) { + o << "else branch"; } else { o << "scope"; @@ -680,6 +683,8 @@ class sema branch(int s, bool r) : start{s}, result{r} { } }; std::vector branches; + bool in_branch = false; + int initialized_in_condition_pos = -1; stack_entry(int p) : pos{p} { } @@ -751,12 +756,10 @@ class sema // Else if we're inside a selection statement but still in the condition // portion (there are no branches entered yet) - else if (std::ssize(selection_stack.back().branches) == 0) { - // If this is a top-level selection statement, handle it the same as - // if we weren't an a selection statement - if (std::ssize(selection_stack) == 1) { + else if (!selection_stack.back().in_branch) { if (sym.assignment_to) { definite_initializations.push_back( sym.identifier ); + selection_stack.back().initialized_in_condition_pos = pos; } else { errors.emplace_back( @@ -764,20 +767,12 @@ class sema "local variable " + name + " is used in a condition before it was initialized"); } - return sym.assignment_to; - } - // Else we can skip the rest of this selection statement, and record - // this as the result of the next outer selection statement's current branch - else { - selection_stack.pop_back(); - assert (std::ssize(selection_stack.back().branches) > 0); - selection_stack.back().branches.back().result = sym.assignment_to; - int this_depth = symbols[pos].depth; - while (symbols[pos + 1].depth >= this_depth) { + int branch_depth = symbols[selection_stack.back().pos].depth; + while (symbols[pos + 1].depth > branch_depth) { ++pos; } - } + --pos; // to handle end of if/else branch } // Else we're in a selection branch and can skip the rest of this branch @@ -792,12 +787,21 @@ class sema "local variable " + name + " is used in a branch before it was initialized"); } + + if (!selection_stack.back().in_branch) { + return sym.assignment_to; + } + selection_stack.back().branches.back().result = sym.assignment_to; // The depth of this branch should always be the depth of // the current selection statement + 1 int branch_depth = symbols[selection_stack.back().pos].depth + 1; - while (symbols[pos + 1].depth > branch_depth) { + while (symbols[pos + 1].depth > branch_depth + 1 + || ( + symbols[pos + 1].depth == branch_depth + 1 && symbols[pos + 1].start + ) + ) { ++pos; } } @@ -822,6 +826,7 @@ class sema auto true_branches = std::string{}; auto false_branches = std::string{}; + for (auto const& b : selection_stack.back().branches) { // If this is not an implicit 'else' branch (i.e., if lineno > 0) @@ -836,9 +841,13 @@ class sema } } + if (auto init_pos = selection_stack.back().initialized_in_condition_pos; init_pos != -1) { + true_branches += "\n branch condition starting at line " + + std::to_string(symbols[init_pos].position().lineno); + } + // If none of the branches was true - if (true_branches.length() == 0) - { + if (true_branches.length() == 0) { selection_stack.pop_back(); // Nothing else to do, just continue } @@ -903,6 +912,11 @@ class sema ) { selection_stack.back().branches.emplace_back( pos, false ); + selection_stack.back().in_branch = true; + } + + if ( !sym.start ) { + selection_stack.back().in_branch = false; } } } @@ -1479,14 +1493,26 @@ class sema assert(active_selections.back()->true_branch); if (active_selections.back()->true_branch.get() == &n) { - kind = compound_sym::is_true; + kind = compound_sym::is_if; } - if ( + else if ( + std::any_of( + std::cbegin(active_selections.back()->else_ifs), + std::cend(active_selections.back()->else_ifs), + [&](const auto& elif) -> bool { + return elif.branch && elif.branch.get() == &n; + } + ) + ) + { + kind = compound_sym::is_else_if; + } + else if ( active_selections.back()->false_branch && active_selections.back()->false_branch.get() == &n ) { - kind = compound_sym::is_false; + kind = compound_sym::is_else; } } return kind;