mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 07:51:05 +01:00
Initial batch of AST matchers. (#953)
Also fixes an issue where llvm casts wouldn't work on AstNode.
This commit is contained in:
@@ -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"],
|
||||
|
||||
@@ -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", {}, ¶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
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
Reference in New Issue
Block a user