diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70cf7b4b77..d8b608e0d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,6 +216,7 @@ set(TINT_TEST_SRCS ast/fallthrough_statement_test.cc ast/function_test.cc ast/identifier_expression_test.cc + ast/if_statement_test.cc ast/import_test.cc ast/int_literal_test.cc ast/location_decoration_test.cc diff --git a/src/ast/else_statement.cc b/src/ast/else_statement.cc index 1ad7a029eb..307daec353 100644 --- a/src/ast/else_statement.cc +++ b/src/ast/else_statement.cc @@ -53,8 +53,15 @@ bool ElseStatement::IsValid() const { void ElseStatement::to_str(std::ostream& out, size_t indent) const { make_indent(out, indent); out << "Else{" << std::endl; - if (condition_ != nullptr) - condition_->to_str(out, indent + 2); + if (condition_ != nullptr) { + make_indent(out, indent + 2); + out << "(" << std::endl; + + condition_->to_str(out, indent + 4); + + make_indent(out, indent + 2); + out << ")" << std::endl; + } make_indent(out, indent + 2); out << "{" << std::endl; diff --git a/src/ast/else_statement_test.cc b/src/ast/else_statement_test.cc index 4f20122090..b9c5f1b2c4 100644 --- a/src/ast/else_statement_test.cc +++ b/src/ast/else_statement_test.cc @@ -110,7 +110,9 @@ TEST_F(ElseStatementTest, ToStr) { std::ostringstream out; e.to_str(out, 2); EXPECT_EQ(out.str(), R"( Else{ - ConstInitializer{true} + ( + ConstInitializer{true} + ) { Nop{} } diff --git a/src/ast/if_statement.cc b/src/ast/if_statement.cc index 364b8288fc..f000f2e461 100644 --- a/src/ast/if_statement.cc +++ b/src/ast/if_statement.cc @@ -35,11 +35,40 @@ IfStatement::IfStatement(const Source& source, IfStatement::~IfStatement() = default; bool IfStatement::IsValid() const { - if (condition_ == nullptr) + if (condition_ == nullptr || !condition_->IsValid()) return false; - if (premerge_.size() > 0 && else_statements_.size() > 1) - return false; + for (const auto& stmt : body_) { + if (stmt == nullptr || !stmt->IsValid()) + return false; + } + + bool found_else = false; + for (const auto& el : else_statements_) { + // Else statement must be last + if (found_else) + return false; + + if (el == nullptr || !el->IsValid()) + return false; + + if (el->condition() == nullptr) + found_else = true; + } + + for (const auto& stmt : premerge_) { + if (stmt == nullptr || !stmt->IsValid()) + return false; + } + + if (premerge_.size() > 0) { + // Premerge only with a single else statement + if (else_statements_.size() != 1) + return false; + // Must be an else, not an elseif + if (else_statements_[0]->condition() != nullptr) + return false; + } return true; } @@ -47,28 +76,43 @@ bool IfStatement::IsValid() const { void IfStatement::to_str(std::ostream& out, size_t indent) const { make_indent(out, indent); out << "If{" << std::endl; - condition_->to_str(out, indent + 2); - out << std::endl; + + // Open if conditional + make_indent(out, indent + 2); + out << "(" << std::endl; + + condition_->to_str(out, indent + 4); + + // Close if conditional + make_indent(out, indent + 2); + out << ")" << std::endl; + + // Open if body make_indent(out, indent + 2); out << "{" << std::endl; for (const auto& stmt : body_) stmt->to_str(out, indent + 4); + // Close the if body make_indent(out, indent + 2); out << "}" << std::endl; + // Close the If + make_indent(out, indent); + out << "}" << std::endl; + for (const auto& e : else_statements_) - e->to_str(out, indent + 2); + e->to_str(out, indent); if (premerge_.size() > 0) { - make_indent(out, indent + 2); + make_indent(out, indent); out << "premerge{" << std::endl; for (const auto& stmt : premerge_) - stmt->to_str(out, indent + 4); + stmt->to_str(out, indent + 2); - make_indent(out, indent + 2); + make_indent(out, indent); out << "}" << std::endl; } } diff --git a/src/ast/if_statement_test.cc b/src/ast/if_statement_test.cc new file mode 100644 index 0000000000..e9990af761 --- /dev/null +++ b/src/ast/if_statement_test.cc @@ -0,0 +1,365 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/ast/if_statement.h" + +#include "gtest/gtest.h" +#include "src/ast/identifier_expression.h" +#include "src/ast/kill_statement.h" +#include "src/ast/nop_statement.h" + +namespace tint { +namespace ast { + +using IfStatementTest = testing::Test; + +TEST_F(IfStatementTest, Creation) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + auto cond_ptr = cond.get(); + auto stmt_ptr = body[0].get(); + + IfStatement stmt(std::move(cond), std::move(body)); + EXPECT_EQ(stmt.condition(), cond_ptr); + ASSERT_EQ(stmt.body().size(), 1); + EXPECT_EQ(stmt.body()[0].get(), stmt_ptr); +} + +TEST_F(IfStatementTest, Creation_WithSource) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + IfStatement stmt(Source{20, 2}, std::move(cond), std::move(body)); + auto src = stmt.source(); + EXPECT_EQ(src.line, 20); + EXPECT_EQ(src.column, 2); +} + +TEST_F(IfStatementTest, IsIf) { + IfStatement stmt; + EXPECT_TRUE(stmt.IsIf()); +} + +TEST_F(IfStatementTest, IsValid) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + EXPECT_TRUE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_WithElseStatements) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts[0]->set_condition(std::make_unique("Ident")); + else_stmts.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + EXPECT_TRUE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_WithPremerge) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + stmt.set_premerge(std::move(premerge)); + EXPECT_TRUE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_MissingCondition) { + std::vector> body; + body.push_back(std::make_unique()); + + IfStatement stmt(nullptr, std::move(body)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_InvalidCondition) { + auto cond = std::make_unique(""); + std::vector> body; + body.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_NullBodyStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + body.push_back(nullptr); + + IfStatement stmt(std::move(cond), std::move(body)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_InvalidBodyStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + body.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_NullElseStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts[0]->set_condition(std::make_unique("Ident")); + else_stmts.push_back(std::make_unique()); + else_stmts.push_back(nullptr); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_InvalidElseStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts[0]->set_condition(std::make_unique("")); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_NullPremergeStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + premerge.push_back(nullptr); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + stmt.set_premerge(std::move(premerge)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_InvalidPremergeStatement) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + stmt.set_premerge(std::move(premerge)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_PremergeWithElseIf) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts[0]->set_condition(std::make_unique("ident")); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + stmt.set_premerge(std::move(premerge)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_PremergeWithoutElse) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_premerge(std::move(premerge)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_MultipleElseWiththoutCondition) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, IsValid_ElseNotLast) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts.push_back(std::make_unique()); + else_stmts[1]->set_condition(std::make_unique("ident")); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + EXPECT_FALSE(stmt.IsValid()); +} + +TEST_F(IfStatementTest, ToStr) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + + std::ostringstream out; + stmt.to_str(out, 2); + EXPECT_EQ(out.str(), R"( If{ + ( + Identifier{cond} + ) + { + Nop{} + } + } +)"); +} + +TEST_F(IfStatementTest, ToStr_WithElseStatements) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_if_body; + else_if_body.push_back(std::make_unique()); + + std::vector> else_body; + else_body.push_back(std::make_unique()); + else_body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + else_stmts[0]->set_condition(std::make_unique("ident")); + else_stmts[0]->set_body(std::move(else_if_body)); + else_stmts.push_back(std::make_unique()); + else_stmts[1]->set_body(std::move(else_body)); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + + std::ostringstream out; + stmt.to_str(out, 2); + EXPECT_EQ(out.str(), R"( If{ + ( + Identifier{cond} + ) + { + Nop{} + } + } + Else{ + ( + Identifier{ident} + ) + { + Kill{} + } + } + Else{ + { + Nop{} + Kill{} + } + } +)"); +} + +TEST_F(IfStatementTest, ToStr_WithPremerge) { + auto cond = std::make_unique("cond"); + std::vector> body; + body.push_back(std::make_unique()); + + std::vector> else_stmts; + else_stmts.push_back(std::make_unique()); + + std::vector> premerge; + premerge.push_back(std::make_unique()); + + IfStatement stmt(std::move(cond), std::move(body)); + stmt.set_else_statements(std::move(else_stmts)); + stmt.set_premerge(std::move(premerge)); + + std::ostringstream out; + stmt.to_str(out, 2); + EXPECT_EQ(out.str(), R"( If{ + ( + Identifier{cond} + ) + { + Nop{} + } + } + Else{ + { + } + } + premerge{ + Nop{} + } +)"); +} + +} // namespace ast +} // namespace tint