diff --git a/common/ostream.h b/common/ostream.h index 5378279eb2b7..7aefc34c8c11 100644 --- a/common/ostream.h +++ b/common/ostream.h @@ -51,7 +51,7 @@ operator<<(std::ostream& out, const T* /*obj*/) -> std::ostream&; // This is important to allow automatic printing of arguments of mocked APIs. template ::value>::type* = nullptr> -void PrintTo(const T* p, std::ostream* out) { +void PrintTo(T* p, std::ostream* out) { *out << static_cast(p); // Also print the object if non-null. diff --git a/executable_semantics/ast/BUILD b/executable_semantics/ast/BUILD index de7bda688978..25b7f0ca9fb0 100644 --- a/executable_semantics/ast/BUILD +++ b/executable_semantics/ast/BUILD @@ -35,6 +35,38 @@ genrule( tools = ["//executable_semantics:gen_rtti"], ) +cc_library( + name = "ast_test_matchers", + testonly = 1, + srcs = [ + "ast_test_matchers_internal.cpp", + "ast_test_matchers_internal.h", + ], + hdrs = ["ast_test_matchers.h"], + deps = [ + ":ast_node", + ":declaration", + ":expression", + ":statement", + "@com_google_googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + +cc_test( + name = "ast_test_matchers_test", + srcs = ["ast_test_matchers_test.cpp"], + deps = [ + ":ast_test_matchers", + ":declaration", + ":expression", + ":pattern", + ":statement", + "//executable_semantics/common:arena", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "declaration", srcs = ["declaration.cpp"], diff --git a/executable_semantics/ast/ast_node.h b/executable_semantics/ast/ast_node.h index 4f45970e9188..49876f2fefdd 100644 --- a/executable_semantics/ast/ast_node.h +++ b/executable_semantics/ast/ast_node.h @@ -7,6 +7,7 @@ #include "executable_semantics/ast/ast_rtti.h" #include "executable_semantics/ast/source_location.h" +#include "llvm/Support/Casting.h" namespace Carbon { @@ -71,4 +72,40 @@ class AstNode { } // namespace Carbon +// Ensure that LLVM casts from AstNode use dynamic_cast, because static_cast +// doesn't work with a virtual base class. +namespace llvm { +template +struct cast_convert_val { + using ResultType = typename cast_retty::ret_type; + static ResultType doit(const Carbon::AstNode* node) { + return dynamic_cast(node); + } +}; + +template +struct cast_convert_val { + using ResultType = typename cast_retty::ret_type; + static ResultType doit(Carbon::AstNode* node) { + return dynamic_cast(node); + } +}; + +template +struct cast_convert_val { + using ResultType = typename cast_retty::ret_type; + static ResultType doit(const Carbon::AstNode& node) { + return dynamic_cast(node); + } +}; + +template +struct cast_convert_val { + using ResultType = typename cast_retty::ret_type; + static ResultType doit(Carbon::AstNode& node) { + return dynamic_cast(node); + } +}; +} // namespace llvm + #endif // EXECUTABLE_SEMANTICS_AST_AST_NODE_H_ diff --git a/executable_semantics/ast/ast_test_matchers.h b/executable_semantics/ast/ast_test_matchers.h new file mode 100644 index 000000000000..b4a4dab8b884 --- /dev/null +++ b/executable_semantics/ast/ast_test_matchers.h @@ -0,0 +1,123 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Googlemock matchers for the AST. Unless otherwise specified, all the +// functions in this file return matchers that can be applied to any +// AstNode or AstNode*. +// +// TODO: Provide matchers for all node Kinds, and establish more uniform +// conventions for them. + +#ifndef EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_H_ +#define EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_H_ + +#include +#include + +#include + +#include "executable_semantics/ast/ast_node.h" +#include "executable_semantics/ast/ast_test_matchers_internal.h" +#include "executable_semantics/ast/expression.h" + +namespace Carbon { + +// Matches a Block node whose .statements() match `matcher`. +inline auto BlockContentsAre( + ::testing::Matcher>> matcher) + -> TestingInternal::BlockContentsMatcher { + return TestingInternal::BlockContentsMatcher(std::move(matcher)); +} + +// Matches a literal with the given value. +// TODO: add overload for string literals +inline auto MatchesLiteral(int value) + -> TestingInternal::MatchesIntLiteralMatcher { + return TestingInternal::MatchesIntLiteralMatcher(value); +} + +// The following functions all match a PrimitiveOperatorExpression with two +// operands that match `lhs` and `rhs` (respectively). The name of the function +// indicates what value of `.op()` they match. +inline auto MatchesMul(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::Mul, std::move(lhs), std::move(rhs)); +} + +inline auto MatchesAdd(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::Add, std::move(lhs), std::move(rhs)); +} + +inline auto MatchesAnd(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::And, std::move(lhs), std::move(rhs)); +} + +inline auto MatchesEq(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::Eq, std::move(lhs), std::move(rhs)); +} + +inline auto MatchesOr(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::Or, std::move(lhs), std::move(rhs)); +} + +inline auto MatchesSub(::testing::Matcher lhs, + ::testing::Matcher rhs) + -> TestingInternal::BinaryOperatorExpressionMatcher { + return TestingInternal::BinaryOperatorExpressionMatcher( + Operator::Sub, std::move(lhs), std::move(rhs)); +} + +// Matches a return statement with no operand. +inline auto MatchesEmptyReturn() -> TestingInternal::MatchesReturnMatcher { + return TestingInternal::MatchesReturnMatcher(); +} + +// Matches a return statement with an explicit operand that matches `matcher`. +inline auto MatchesReturn(::testing::Matcher matcher) + -> TestingInternal::MatchesReturnMatcher { + return TestingInternal::MatchesReturnMatcher(matcher); +} + +// Matches a FunctionDeclaration. By default the returned object matches any +// FunctionDeclaration, but it has methods for restricting the match, which can +// be chained fluent-style: +// +// EXPECT_THAT(node, MatchesFunctionDeclaration() +// .WithName("Foo") +// .WithBody(BlockContentsAre(...))); +// +// The available methods are: +// +// // *this only matches if the declared name matches name_matcher. +// WithName(::testing::Matcher name_matcher) +// +// // *this only matches if the declaration has a body that matches +// // body_matcher. +// WithBody(::testing::Matcher body_matcher) +// +// TODO: Add method for matching only if the declaration has no body. +// TODO: Add methods for matching parameters, deduced parameters, +// and return term. +inline auto MatchesFunctionDeclaration() + -> TestingInternal::MatchesFunctionDeclarationMatcher { + return TestingInternal::MatchesFunctionDeclarationMatcher(); +} + +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_H_ diff --git a/executable_semantics/ast/ast_test_matchers_internal.cpp b/executable_semantics/ast/ast_test_matchers_internal.cpp new file mode 100644 index 000000000000..49a575d92e4a --- /dev/null +++ b/executable_semantics/ast/ast_test_matchers_internal.cpp @@ -0,0 +1,189 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "executable_semantics/ast/ast_test_matchers_internal.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/raw_ostream.h" + +namespace Carbon { +namespace TestingInternal { + +auto BlockContentsMatcher::MatchAndExplain( + Nonnull node, ::testing::MatchResultListener* out) const + -> bool { + const auto* block = llvm::dyn_cast(node); + if (block == nullptr) { + *out << "is not a Block"; + return false; + } + *out << "is a Block whose statements collection "; + return matcher_.MatchAndExplain(block->statements(), out); +} + +auto MatchesIntLiteralMatcher::MatchAndExplain( + const AstNode* node, ::testing::MatchResultListener* listener) const + -> bool { + const auto* literal = llvm::dyn_cast(node); + if (literal == nullptr) { + *listener << "is not an IntLiteral"; + return false; + } + bool matched = literal->value() == value_; + *listener << "is " << (matched ? "" : "not ") << "a literal " << value_; + return matched; +} + +auto BinaryOperatorExpressionMatcher::MatchAndExplain( + Nonnull node, ::testing::MatchResultListener* out) const + -> bool { + const auto* op = llvm::dyn_cast(node); + if (op == nullptr) { + *out << "which is not a PrimitiveOperatorExpression"; + return false; + } + if (op->arguments().size() != 2) { + *out << "which does not have two operands"; + return false; + } + if (op->op() != op_) { + *out << "whose operator is not " << ToString(op_); + return false; + } + *out << "which is a " << ToString(op_) << " expression whose left operand "; + bool matched = lhs_.MatchAndExplain(*op->arguments()[0], out); + *out << " and right operand "; + if (!rhs_.MatchAndExplain(*op->arguments()[1], out)) { + matched = false; + } + return matched; +} + +void BinaryOperatorExpressionMatcher::DescribeToImpl(std::ostream* out, + bool negated) const { + *out << "is " << (negated ? "not " : "") << "a " << ToString(op_) + << " expression whose "; + *out << "left operand "; + lhs_.DescribeTo(out); + *out << " and right operand "; + rhs_.DescribeTo(out); +} + +auto MatchesReturnMatcher::MatchAndExplain( + const AstNode* node, ::testing::MatchResultListener* listener) const + -> bool { + const auto* ret = llvm::dyn_cast(node); + if (ret == nullptr) { + *listener << "which is not a return statement"; + return false; + } + *listener << "which is a return statement "; + if (ret->is_omitted_expression()) { + *listener << "with no operand"; + return !matcher_.has_value(); + } else if (matcher_.has_value()) { + *listener << "whose operand "; + return matcher_->MatchAndExplain(ret->expression(), listener); + } else { + *listener << "that has an operand"; + return false; + } +} + +void MatchesReturnMatcher::DescribeToImpl(std::ostream* out, + bool negated) const { + *out << "is " << (negated ? "not " : "") << "a return statement "; + if (matcher_.has_value()) { + *out << "whose operand "; + matcher_->DescribeTo(out); + } else { + *out << "with no operand"; + } +} + +namespace { +// llvm::raw_ostream implementation backed by a MatchResultListener, so +// we can use tools like llvm::ListSeparator. +class RawListenerOstream : public llvm::raw_ostream { + public: + explicit RawListenerOstream(Nonnull<::testing::MatchResultListener*> listener) + : out_(listener->stream()), fake_pos_(0) {} + + void write_impl(const char* ptr, size_t size) override { + if (out_ == nullptr) { + fake_pos_ += size; + } else { + out_->write(ptr, size); + } + } + + auto current_pos() const -> uint64_t override { + if (out_ == nullptr) { + return fake_pos_; + } else { + return out_->tellp(); + } + } + + ~RawListenerOstream() override { flush(); } + + private: + std::ostream* out_; + // fake_pos_ tracks the notional output position when out_ is null. + uint64_t fake_pos_; +}; +} // namespace + +auto MatchesFunctionDeclarationMatcher::MatchAndExplain( + const AstNode* node, ::testing::MatchResultListener* listener) const + -> bool { + RawListenerOstream out(listener); + const auto* decl = llvm::dyn_cast(node); + if (decl == nullptr) { + out << "which is not a function declaration"; + return false; + } + out << "which is a function declaration "; + llvm::ListSeparator sep(", and"); + if (name_matcher_.has_value()) { + out << sep << "whose name "; + if (!name_matcher_->MatchAndExplain(decl->name(), listener)) { + // We short-circuit here because if the name doesn't match, that's + // probably the only information the user cares about. + return false; + } + } + bool matched = true; + if (body_matcher_.has_value()) { + out << sep; + if (!decl->body().has_value()) { + out << "that doesn't have a body"; + matched = false; + } else { + out << "whose body "; + if (!body_matcher_->MatchAndExplain(**decl->body(), listener)) { + matched = false; + } + } + } + return matched; +} + +void MatchesFunctionDeclarationMatcher::DescribeToImpl(std::ostream* out, + bool negated) const { + llvm::raw_os_ostream raw_out(*out); + raw_out << "is " << (negated ? "not " : "") << "a function declaration "; + llvm::ListSeparator sep(", and"); + if (name_matcher_.has_value()) { + raw_out << sep << "whose name "; + name_matcher_->DescribeTo(out); + } + if (body_matcher_.has_value()) { + raw_out << sep << "whose body "; + body_matcher_->DescribeTo(out); + } +} + +} // namespace TestingInternal +} // namespace Carbon diff --git a/executable_semantics/ast/ast_test_matchers_internal.h b/executable_semantics/ast/ast_test_matchers_internal.h new file mode 100644 index 000000000000..eb8e60683f3e --- /dev/null +++ b/executable_semantics/ast/ast_test_matchers_internal.h @@ -0,0 +1,207 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Implementation details of the functions in ast_test_matchers.h. + +#ifndef EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_INTERNAL_H_ +#define EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_INTERNAL_H_ + +#include +#include + +#include + +#include "executable_semantics/ast/ast_node.h" +#include "executable_semantics/ast/declaration.h" +#include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/statement.h" +#include "llvm/Support/Casting.h" + +namespace Carbon { +namespace TestingInternal { + +// Matches a Block based on its contents. +class BlockContentsMatcher { + public: + using is_gtest_matcher = void; + + // Constructs a matcher which matches a Block node whose .statements() matches + // `matcher`. + explicit BlockContentsMatcher( + ::testing::Matcher>> matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* out) const { + *out << "is a Block whose statements collection "; + matcher_.DescribeTo(out); + } + + void DescribeNegationTo(std::ostream* out) const { + *out << "is not a Block whose statements collection "; + matcher_.DescribeTo(out); + } + + auto MatchAndExplain(const AstNode& node, + ::testing::MatchResultListener* out) const -> bool { + return MatchAndExplain(&node, out); + } + + auto MatchAndExplain(Nonnull node, + ::testing::MatchResultListener* out) const -> bool; + + private: + testing::Matcher>> matcher_; +}; + +// Matches an IntLiteral. +class MatchesIntLiteralMatcher { + public: + using is_gtest_matcher = void; + + // Constructs a matcher which matches an IntLiteral whose value() is `value`. + explicit MatchesIntLiteralMatcher(int value) : value_(value) {} + + void DescribeTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/false); + } + + void DescribeNegationTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/true); + } + + auto MatchAndExplain(const AstNode& node, + ::testing::MatchResultListener* listener) const -> bool { + return MatchAndExplain(&node, listener); + } + + auto MatchAndExplain(const AstNode* node, + ::testing::MatchResultListener* listener) const -> bool; + + private: + void DescribeToImpl(std::ostream* out, bool negated) const { + *out << "is " << (negated ? "not " : "") << "a literal " << value_; + } + + int value_; +}; + +// Matches a PrimitiveOperatorExpression that has two operands. +class BinaryOperatorExpressionMatcher { + public: + using is_gtest_matcher = void; + + // Constructs a matcher which matches a PrimitiveOperatorExpression whose + // operator is `op`, and which has two operands that match `lhs` and `rhs` + // respectively. + explicit BinaryOperatorExpressionMatcher(Operator op, + ::testing::Matcher lhs, + ::testing::Matcher rhs) + : op_(op), lhs_(std::move(lhs)), rhs_(std::move(rhs)) {} + + void DescribeTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/false); + } + + void DescribeNegationTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/true); + } + + auto MatchAndExplain(const AstNode& node, + ::testing::MatchResultListener* out) const -> bool { + return MatchAndExplain(&node, out); + } + + auto MatchAndExplain(Nonnull node, + ::testing::MatchResultListener* out) const -> bool; + + private: + void DescribeToImpl(std::ostream* out, bool negated) const; + + Operator op_; + ::testing::Matcher lhs_; + ::testing::Matcher rhs_; +}; + +// Matches a Return node. +class MatchesReturnMatcher { + public: + using is_gtest_matcher = void; + + // Constructs a matcher which matches a Return statement that has no operand. + explicit MatchesReturnMatcher() = default; + + // Constructs a matcher which matches a Return statement that has an explicit + // operand that matches `matcher`. + explicit MatchesReturnMatcher(::testing::Matcher matcher) + : matcher_(std::move(matcher)) {} + + void DescribeTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/false); + } + + void DescribeNegationTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/true); + } + + auto MatchAndExplain(const AstNode& node, + ::testing::MatchResultListener* listener) const -> bool { + return MatchAndExplain(&node, listener); + } + + auto MatchAndExplain(const AstNode* node, + ::testing::MatchResultListener* listener) const -> bool; + + private: + void DescribeToImpl(std::ostream* out, bool negated) const; + + std::optional<::testing::Matcher> matcher_; +}; + +// Matches a FunctionDeclaration. See documentation for +// MatchesFunctionDeclaration in ast_test_matchers.h. +class MatchesFunctionDeclarationMatcher { + public: + using is_gtest_matcher = void; + + MatchesFunctionDeclarationMatcher() = default; + + auto WithName(::testing::Matcher name_matcher) + -> MatchesFunctionDeclarationMatcher& { + name_matcher_ = std::move(name_matcher); + return *this; + } + + auto WithBody(::testing::Matcher body_matcher) + -> MatchesFunctionDeclarationMatcher& { + body_matcher_ = std::move(body_matcher); + return *this; + } + + void DescribeTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/false); + } + + void DescribeNegationTo(std::ostream* out) const { + DescribeToImpl(out, /*negated=*/true); + } + + auto MatchAndExplain(const AstNode& node, + ::testing::MatchResultListener* listener) const -> bool { + return MatchAndExplain(&node, listener); + } + + auto MatchAndExplain(const AstNode* node, + ::testing::MatchResultListener* listener) const -> bool; + + private: + void DescribeToImpl(std::ostream* out, bool negated) const; + + std::optional<::testing::Matcher> name_matcher_; + std::optional<::testing::Matcher> body_matcher_; +}; + +} // namespace TestingInternal +} // namespace Carbon + +#endif // EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_INTERNAL_H_ diff --git a/executable_semantics/ast/ast_test_matchers_test.cpp b/executable_semantics/ast/ast_test_matchers_test.cpp new file mode 100644 index 000000000000..4f1f45baff25 --- /dev/null +++ b/executable_semantics/ast/ast_test_matchers_test.cpp @@ -0,0 +1,128 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "executable_semantics/ast/ast_test_matchers.h" + +#include +#include + +#include "executable_semantics/ast/declaration.h" +#include "executable_semantics/ast/expression.h" +#include "executable_semantics/ast/pattern.h" +#include "executable_semantics/ast/statement.h" +#include "executable_semantics/common/arena.h" + +namespace Carbon { +namespace { + +using ::testing::_; +using ::testing::IsEmpty; +using ::testing::Not; + +static constexpr SourceLocation DummyLoc("dummy", 0); + +TEST(BlockContentsAreTest, BasicUsage) { + Block empty_block(DummyLoc, {}); + EXPECT_THAT(empty_block, BlockContentsAre(IsEmpty())); + EXPECT_THAT(&empty_block, BlockContentsAre(IsEmpty())); + + Break break_node(DummyLoc); + EXPECT_THAT(break_node, Not(BlockContentsAre(_))); + + Block break_block(DummyLoc, {&break_node}); + EXPECT_THAT(break_block, Not(BlockContentsAre(IsEmpty()))); +} + +TEST(MatchesLiteralTest, BasicUsage) { + IntLiteral literal(DummyLoc, 42); + EXPECT_THAT(literal, MatchesLiteral(42)); + EXPECT_THAT(&literal, MatchesLiteral(42)); + EXPECT_THAT(literal, Not(MatchesLiteral(43))); + EXPECT_THAT(StringLiteral(DummyLoc, "foo"), Not(MatchesLiteral(42))); +} + +TEST(MatchesMulTest, BasicUsage) { + IntLiteral two(DummyLoc, 2); + IntLiteral three(DummyLoc, 3); + PrimitiveOperatorExpression mul(DummyLoc, Operator::Mul, {&two, &three}); + EXPECT_THAT(mul, MatchesMul(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT(&mul, MatchesMul(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT(mul, MatchesMul(_, _)); + EXPECT_THAT(mul, Not(MatchesMul(MatchesLiteral(2), MatchesLiteral(2)))); + EXPECT_THAT(StringLiteral(DummyLoc, "foo"), Not(MatchesMul(_, _))); + EXPECT_THAT(PrimitiveOperatorExpression(DummyLoc, Operator::Deref, {&two}), + Not(MatchesMul(_, _))); + + PrimitiveOperatorExpression nested(DummyLoc, Operator::Mul, {&two, &mul}); + EXPECT_THAT(nested, + MatchesMul(MatchesLiteral(2), + MatchesMul(MatchesLiteral(2), MatchesLiteral(3)))); +} + +TEST(MatchesBinaryOpTest, BasicUsage) { + IntLiteral two(DummyLoc, 2); + IntLiteral three(DummyLoc, 3); + + // Testing of MatchesMul provides most of the coverage for these matchers, + // since they are thin wrappers around a common implementation. We only test + // the others enough to detect copy-paste errors in the wrappers. + EXPECT_THAT( + PrimitiveOperatorExpression(DummyLoc, Operator::Add, {&two, &three}), + MatchesAdd(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT( + PrimitiveOperatorExpression(DummyLoc, Operator::And, {&two, &three}), + MatchesAnd(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT( + PrimitiveOperatorExpression(DummyLoc, Operator::Eq, {&two, &three}), + MatchesEq(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT( + PrimitiveOperatorExpression(DummyLoc, Operator::Or, {&two, &three}), + MatchesOr(MatchesLiteral(2), MatchesLiteral(3))); + EXPECT_THAT( + PrimitiveOperatorExpression(DummyLoc, Operator::Sub, {&two, &three}), + MatchesSub(MatchesLiteral(2), MatchesLiteral(3))); +} + +TEST(MatchesReturnTest, BasicUsage) { + TupleLiteral unit(DummyLoc); + Return empty_return(DummyLoc, &unit, /*is_omitted_expression=*/true); + EXPECT_THAT(empty_return, MatchesEmptyReturn()); + EXPECT_THAT(&empty_return, MatchesEmptyReturn()); + EXPECT_THAT(empty_return, Not(MatchesReturn(_))); + + IntLiteral int_val(DummyLoc, 42); + Return explicit_return(DummyLoc, &int_val, /*is_omitted_expression=*/false); + EXPECT_THAT(explicit_return, MatchesReturn(MatchesLiteral(42))); + EXPECT_THAT(explicit_return, Not(MatchesEmptyReturn())); + + EXPECT_THAT(int_val, Not(MatchesEmptyReturn())); + EXPECT_THAT(int_val, Not(MatchesReturn(_))); +} + +TEST(MatchesFunctionDeclarationTest, BasicUsage) { + TuplePattern params(DummyLoc, {}); + Block body(DummyLoc, {}); + FunctionDeclaration decl(DummyLoc, "Foo", {}, ¶ms, + ReturnTerm::Omitted(DummyLoc), &body); + + EXPECT_THAT(decl, MatchesFunctionDeclaration()); + EXPECT_THAT(&decl, MatchesFunctionDeclaration()); + EXPECT_THAT(decl, MatchesFunctionDeclaration().WithName("Foo")); + EXPECT_THAT(decl, MatchesFunctionDeclaration().WithBody(_)); + EXPECT_THAT(decl, MatchesFunctionDeclaration().WithName("Foo").WithBody(_)); + EXPECT_THAT(decl, MatchesFunctionDeclaration().WithBody(_).WithName("Foo")); + EXPECT_THAT(decl, Not(MatchesFunctionDeclaration().WithName("Bar"))); + EXPECT_THAT(decl, + Not(MatchesFunctionDeclaration().WithBody(MatchesLiteral(0)))); + + FunctionDeclaration forward_decl(DummyLoc, "Foo", {}, ¶ms, + ReturnTerm::Omitted(DummyLoc), std::nullopt); + EXPECT_THAT(forward_decl, MatchesFunctionDeclaration().WithName("Foo")); + EXPECT_THAT(forward_decl, Not(MatchesFunctionDeclaration().WithBody(_))); + + EXPECT_THAT(body, Not(MatchesFunctionDeclaration())); +} + +} // namespace +} // namespace Carbon diff --git a/executable_semantics/ast/expression.cpp b/executable_semantics/ast/expression.cpp index 581233fab503..2b7a785b6e7b 100644 --- a/executable_semantics/ast/expression.cpp +++ b/executable_semantics/ast/expression.cpp @@ -36,32 +36,25 @@ auto TupleExpressionFromParenContents( Expression::~Expression() = default; -static void PrintOp(llvm::raw_ostream& out, Operator op) { +auto ToString(Operator op) -> std::string_view { switch (op) { case Operator::Add: - out << "+"; - break; + return "+"; case Operator::Neg: case Operator::Sub: - out << "-"; - break; + return "-"; case Operator::Mul: case Operator::Deref: case Operator::Ptr: - out << "*"; - break; + return "*"; case Operator::Not: - out << "not"; - break; + return "not"; case Operator::And: - out << "and"; - break; + return "and"; case Operator::Or: - out << "or"; - break; + return "or"; case Operator::Eq: - out << "=="; - break; + return "=="; } } @@ -117,16 +110,14 @@ void Expression::Print(llvm::raw_ostream& out) const { const auto& op = cast(*this); switch (op.arguments().size()) { case 0: - PrintOp(out, op.op()); + out << ToString(op.op()); break; case 1: - PrintOp(out, op.op()); - out << " " << *op.arguments()[0]; + out << ToString(op.op()) << " " << *op.arguments()[0]; break; case 2: - out << *op.arguments()[0] << " "; - PrintOp(out, op.op()); - out << " " << *op.arguments()[1]; + out << *op.arguments()[0] << " " << ToString(op.op()) << " " + << *op.arguments()[1]; break; default: FATAL() << "Unexpected argument count: " << op.arguments().size(); diff --git a/executable_semantics/ast/expression.h b/executable_semantics/ast/expression.h index a4eb23f9ab5d..aa3db9469ad9 100644 --- a/executable_semantics/ast/expression.h +++ b/executable_semantics/ast/expression.h @@ -93,6 +93,9 @@ enum class Operator { Ptr, }; +// Returns the lexical representation of `op`, such as "+" for `Add`. +auto ToString(Operator op) -> std::string_view; + class IdentifierExpression : public Expression { public: explicit IdentifierExpression(SourceLocation source_loc, std::string name) diff --git a/executable_semantics/ast/source_location.h b/executable_semantics/ast/source_location.h index aab6ce4e5c61..ec0fa4e6e737 100644 --- a/executable_semantics/ast/source_location.h +++ b/executable_semantics/ast/source_location.h @@ -16,7 +16,7 @@ namespace Carbon { class SourceLocation { public: // The filename should be eternal or arena-allocated to eliminate copies. - SourceLocation(const char* filename, int line_num) + constexpr SourceLocation(const char* filename, int line_num) : filename_(filename), line_num_(line_num) {} SourceLocation(Nonnull filename, int line_num) : filename_(filename->c_str()), line_num_(line_num) {}