Initial batch of AST matchers. (#953)

Also fixes an issue where llvm casts wouldn't work on AstNode.
This commit is contained in:
Geoff Romer
2021-11-23 12:34:55 -08:00
committed by GitHub
parent ec2f8e64e2
commit 6ae85bd13a
10 changed files with 733 additions and 23 deletions
+32
View File
@@ -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"],
+37
View File
@@ -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 <typename To>
struct cast_convert_val<To, const Carbon::AstNode*, const Carbon::AstNode*> {
using ResultType = typename cast_retty<To, const Carbon::AstNode*>::ret_type;
static ResultType doit(const Carbon::AstNode* node) {
return dynamic_cast<ResultType>(node);
}
};
template <typename To>
struct cast_convert_val<To, Carbon::AstNode*, Carbon::AstNode*> {
using ResultType = typename cast_retty<To, Carbon::AstNode*>::ret_type;
static ResultType doit(Carbon::AstNode* node) {
return dynamic_cast<ResultType>(node);
}
};
template <typename To>
struct cast_convert_val<To, const Carbon::AstNode, const Carbon::AstNode> {
using ResultType = typename cast_retty<To, const Carbon::AstNode>::ret_type;
static ResultType doit(const Carbon::AstNode& node) {
return dynamic_cast<ResultType>(node);
}
};
template <typename To>
struct cast_convert_val<To, Carbon::AstNode, Carbon::AstNode> {
using ResultType = typename cast_retty<To, Carbon::AstNode>::ret_type;
static ResultType doit(Carbon::AstNode& node) {
return dynamic_cast<ResultType>(node);
}
};
} // namespace llvm
#endif // EXECUTABLE_SEMANTICS_AST_AST_NODE_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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <ostream>
#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<llvm::ArrayRef<Nonnull<const Statement*>>> 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<AstNode> lhs,
::testing::Matcher<AstNode> rhs)
-> TestingInternal::BinaryOperatorExpressionMatcher {
return TestingInternal::BinaryOperatorExpressionMatcher(
Operator::Mul, std::move(lhs), std::move(rhs));
}
inline auto MatchesAdd(::testing::Matcher<AstNode> lhs,
::testing::Matcher<AstNode> rhs)
-> TestingInternal::BinaryOperatorExpressionMatcher {
return TestingInternal::BinaryOperatorExpressionMatcher(
Operator::Add, std::move(lhs), std::move(rhs));
}
inline auto MatchesAnd(::testing::Matcher<AstNode> lhs,
::testing::Matcher<AstNode> rhs)
-> TestingInternal::BinaryOperatorExpressionMatcher {
return TestingInternal::BinaryOperatorExpressionMatcher(
Operator::And, std::move(lhs), std::move(rhs));
}
inline auto MatchesEq(::testing::Matcher<AstNode> lhs,
::testing::Matcher<AstNode> rhs)
-> TestingInternal::BinaryOperatorExpressionMatcher {
return TestingInternal::BinaryOperatorExpressionMatcher(
Operator::Eq, std::move(lhs), std::move(rhs));
}
inline auto MatchesOr(::testing::Matcher<AstNode> lhs,
::testing::Matcher<AstNode> rhs)
-> TestingInternal::BinaryOperatorExpressionMatcher {
return TestingInternal::BinaryOperatorExpressionMatcher(
Operator::Or, std::move(lhs), std::move(rhs));
}
inline auto MatchesSub(::testing::Matcher<AstNode> lhs,
::testing::Matcher<AstNode> 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<AstNode> 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<std::string> name_matcher)
//
// // *this only matches if the declaration has a body that matches
// // body_matcher.
// WithBody(::testing::Matcher<AstNode> 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_
@@ -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<const AstNode*> node, ::testing::MatchResultListener* out) const
-> bool {
const auto* block = llvm::dyn_cast<Block>(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<IntLiteral>(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<const AstNode*> node, ::testing::MatchResultListener* out) const
-> bool {
const auto* op = llvm::dyn_cast<PrimitiveOperatorExpression>(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<Return>(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<FunctionDeclaration>(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
@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <ostream>
#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<llvm::ArrayRef<Nonnull<const Statement*>>> 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<const AstNode*> node,
::testing::MatchResultListener* out) const -> bool;
private:
testing::Matcher<llvm::ArrayRef<Nonnull<const Statement*>>> 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<AstNode> lhs,
::testing::Matcher<AstNode> 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<const AstNode*> node,
::testing::MatchResultListener* out) const -> bool;
private:
void DescribeToImpl(std::ostream* out, bool negated) const;
Operator op_;
::testing::Matcher<AstNode> lhs_;
::testing::Matcher<AstNode> 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<AstNode> 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<AstNode>> 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<std::string> name_matcher)
-> MatchesFunctionDeclarationMatcher& {
name_matcher_ = std::move(name_matcher);
return *this;
}
auto WithBody(::testing::Matcher<AstNode> 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<std::string>> name_matcher_;
std::optional<::testing::Matcher<AstNode>> body_matcher_;
};
} // namespace TestingInternal
} // namespace Carbon
#endif // EXECUTABLE_SEMANTICS_AST_AST_TEST_MATCHERS_INTERNAL_H_
@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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", {}, &params,
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", {}, &params,
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
+12 -21
View File
@@ -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<PrimitiveOperatorExpression>(*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();
+3
View File
@@ -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)
+1 -1
View File
@@ -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<const std::string*> filename, int line_num)
: filename_(filename->c_str()), line_num_(line_num) {}