diff --git a/toolchain/parser/BUILD b/toolchain/parser/BUILD index cb03f4aa937b..6ff6acd55dc4 100644 --- a/toolchain/parser/BUILD +++ b/toolchain/parser/BUILD @@ -11,7 +11,10 @@ cc_library( srcs = ["parse_node_kind.cpp"], hdrs = ["parse_node_kind.h"], textual_hdrs = ["parse_node_kind.def"], - deps = ["@llvm-project//llvm:Support"], + deps = [ + "//common:ostream", + "@llvm-project//llvm:Support", + ], ) cc_test( diff --git a/toolchain/parser/parse_node_kind.h b/toolchain/parser/parse_node_kind.h index 657c53905974..5bad9886e73b 100644 --- a/toolchain/parser/parse_node_kind.h +++ b/toolchain/parser/parse_node_kind.h @@ -8,6 +8,7 @@ #include #include +#include "common/ostream.h" #include "llvm/ADT/StringRef.h" namespace Carbon { @@ -60,6 +61,8 @@ class ParseNodeKind { // NOLINTNEXTLINE(google-explicit-constructor) constexpr operator KindEnum() const { return kind_; } + void Print(llvm::raw_ostream& out) const { out << name(); } + private: constexpr explicit ParseNodeKind(KindEnum k) : kind_(k) {} diff --git a/toolchain/parser/parse_tree.cpp b/toolchain/parser/parse_tree.cpp index 3c98b3fa0dc2..657d45b75c9f 100644 --- a/toolchain/parser/parse_tree.cpp +++ b/toolchain/parser/parse_tree.cpp @@ -75,6 +75,11 @@ auto ParseTree::node_token(Node n) const -> TokenizedBuffer::Token { return node_impls_[n.index_].token; } +auto ParseTree::node_subtree_size(Node n) const -> int32_t { + CARBON_CHECK(n.is_valid()); + return node_impls_[n.index_].subtree_size; +} + auto ParseTree::GetNodeText(Node n) const -> llvm::StringRef { CARBON_CHECK(n.is_valid()); return tokens_->GetTokenText(node_impls_[n.index_].token); diff --git a/toolchain/parser/parse_tree.h b/toolchain/parser/parse_tree.h index 9d761d2ed1ba..df1ce6b44446 100644 --- a/toolchain/parser/parse_tree.h +++ b/toolchain/parser/parse_tree.h @@ -94,6 +94,8 @@ class ParseTree { // Returns the token the given parse tree node models. [[nodiscard]] auto node_token(Node n) const -> TokenizedBuffer::Token; + [[nodiscard]] auto node_subtree_size(Node n) const -> int32_t; + // Returns the text backing the token for the given node. // // This is a convenience method for chaining from a node through its token to diff --git a/toolchain/semantics/BUILD b/toolchain/semantics/BUILD index 7a67ae72500c..d8495b87e406 100644 --- a/toolchain/semantics/BUILD +++ b/toolchain/semantics/BUILD @@ -5,9 +5,34 @@ package(default_visibility = ["//visibility:public"]) cc_library( - name = "function", - hdrs = ["function.h"], - deps = ["//toolchain/parser:parse_tree"], + name = "nodes", + hdrs = [ + "nodes/declared_name.h", + "nodes/expression_statement.h", + "nodes/function.h", + "nodes/infix_operator.h", + "nodes/literal.h", + "meta_node.h", + "meta_node_block.h", + "nodes/pattern_binding.h", + "nodes/return.h", + ], + deps = [ + "//common:check", + "//common:ostream", + "//toolchain/parser:parse_tree", + "@llvm-project//llvm:Support", + ], +) + +cc_library( + name = "parse_subtree_consumer", + srcs = ["parse_subtree_consumer.cpp"], + hdrs = ["parse_subtree_consumer.h"], + deps = [ + "//common:check", + "//toolchain/parser:parse_tree", + ], ) cc_library( @@ -15,7 +40,7 @@ cc_library( srcs = ["semantics_ir.cpp"], hdrs = ["semantics_ir.h"], deps = [ - ":function", + ":nodes", "//common:check", "//toolchain/lexer:tokenized_buffer", "//toolchain/parser:parse_tree", @@ -28,6 +53,8 @@ cc_library( srcs = ["semantics_ir_factory.cpp"], hdrs = ["semantics_ir_factory.h"], deps = [ + ":nodes", + ":parse_subtree_consumer", ":semantics_ir", "//common:check", "//toolchain/lexer:tokenized_buffer", @@ -37,12 +64,37 @@ cc_library( ], ) +cc_library( + name = "semantics_ir_test_helpers", + testonly = 1, + srcs = ["semantics_ir_for_test.cpp"], + hdrs = [ + "nodes/declared_name_test_matchers.h", + "nodes/function_test_matchers.h", + "nodes/infix_operator_test_matchers.h", + "nodes/literal_test_matchers.h", + "nodes/pattern_binding_test_matchers.h", + "nodes/return_test_matchers.h", + "semantics_ir_for_test.h", + "semantics_ir_test_helpers.h", + ], + deps = [ + ":nodes", + ":semantics_ir", + "//common:check", + "//common:ostream", + "@com_google_googletest//:gtest", + "@llvm-project//llvm:Support", + ], +) + cc_test( name = "semantics_ir_factory_test", size = "small", srcs = ["semantics_ir_factory_test.cpp"], deps = [ ":semantics_ir_factory", + ":semantics_ir_test_helpers", "//common:gtest_main", "//toolchain/diagnostics:mocks", "//toolchain/lexer:tokenized_buffer", diff --git a/toolchain/semantics/function.h b/toolchain/semantics/function.h deleted file mode 100644 index e94916341ab0..000000000000 --- a/toolchain/semantics/function.h +++ /dev/null @@ -1,31 +0,0 @@ -// 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 - -#ifndef CARBON_TOOLCHAIN_SEMANTICS_FUNCTION_H_ -#define CARBON_TOOLCHAIN_SEMANTICS_FUNCTION_H_ - -#include "toolchain/parser/parse_tree.h" - -namespace Carbon::Semantics { - -// Semantic information for a function. -class Function { - public: - Function(ParseTree::Node decl_node, ParseTree::Node name_node) - : decl_node_(decl_node), name_node_(name_node) {} - - auto decl_node() const -> ParseTree::Node { return decl_node_; } - auto name_node() const -> ParseTree::Node { return name_node_; } - - private: - // The FunctionDeclaration node. - ParseTree::Node decl_node_; - - // The function's DeclaredName node. - ParseTree::Node name_node_; -}; - -} // namespace Carbon::Semantics - -#endif // CARBON_TOOLCHAIN_SEMANTICS_FUNCTION_H_ diff --git a/toolchain/semantics/meta_node.h b/toolchain/semantics/meta_node.h new file mode 100644 index 000000000000..03b0333cd266 --- /dev/null +++ b/toolchain/semantics/meta_node.h @@ -0,0 +1,115 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_META_NODE_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_META_NODE_H_ + +#include +#include + +#include "common/check.h" +#include "llvm/ADT/SmallVector.h" + +namespace Carbon { +class SemanticsIR; +} // namespace Carbon + +namespace Carbon::Testing { +class SemanticsIRForTest; +} // namespace Carbon::Testing + +namespace Carbon::Semantics { + +// The standard structure for nodes which have multiple subtypes. +// +// This flyweight pattern is used so that each subtype can be stored in its own +// vector, minimizing memory consumption and heap fragmentation when large +// quantities are being created. +template +class MetaNode { + public: + MetaNode() : MetaNode(KindT::Invalid, -1) {} + + auto kind() -> KindT { return kind_; } + + private: + friend MetaNodeStoreT; + + MetaNode(KindT kind, int32_t index) : kind_(kind), index_(index) {} + + KindT kind_; + + // The index of the named entity within its list. + int32_t index_; +}; + +// Provides storage for nodes, indexed by MetaNodes. +template +class MetaNodeStore { + public: + using MetaNodeT = MetaNode>; + + // Stores the provided node, returning a pointer to it. + template + auto Store(NodeT node) -> MetaNodeT { + auto& node_store = + std::get(NodeT::MetaNodeKind)>(node_stores_); + int32_t index = node_store.size(); + node_store.push_back(node); + return MetaNodeT(NodeT::MetaNodeKind, index); + } + + // Returns the requested node. Requires that the pointer is valid for this + // store. + template + auto Get(MetaNodeT meta_node) const -> const NodeT& { + CARBON_CHECK(meta_node.index_ >= 0); + CARBON_CHECK(meta_node.kind_ == NodeT::MetaNodeKind) + << "Kind mismatch: " << static_cast(meta_node.kind_) << " vs " + << static_cast(NodeT::MetaNodeKind); + auto& node_store = + std::get(NodeT::MetaNodeKind)>(node_stores_); + CARBON_CHECK(static_cast(meta_node.index_) < node_store.size()); + return node_store[meta_node.index_]; + } + + private: + std::tuple...> node_stores_; +}; + +// Meta node information for declarations. +enum class DeclarationKind { + Function, + Invalid, +}; +class Function; +using DeclarationStore = MetaNodeStore; +using Declaration = MetaNode; + +// Meta node information for statements. +enum class StatementKind { + ExpressionStatement, + Return, + Invalid, +}; +class ExpressionStatement; +class Return; +using StatementStore = + MetaNodeStore; +using Statement = MetaNode; + +// Meta node information for declarations. +enum class ExpressionKind { + InfixOperator, + Literal, + Invalid, +}; +class InfixOperator; +class Literal; +using ExpressionStore = MetaNodeStore; +using Expression = MetaNode; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_META_NODE_H_ diff --git a/toolchain/semantics/meta_node_block.h b/toolchain/semantics/meta_node_block.h new file mode 100644 index 000000000000..0945469418fc --- /dev/null +++ b/toolchain/semantics/meta_node_block.h @@ -0,0 +1,38 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_META_NODE_BLOCK_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_META_NODE_BLOCK_H_ + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "toolchain/semantics/meta_node.h" + +namespace Carbon::Semantics { + +// The standard structure for declaration and statement blocks. +template +struct MetaNodeBlock { + public: + MetaNodeBlock(llvm::SmallVector nodes, + llvm::StringMap name_lookup) + : nodes_(std::move(nodes)), name_lookup_(std::move(name_lookup)) {} + + auto nodes() const -> llvm::ArrayRef { return nodes_; } + auto name_lookup() const -> const llvm::StringMap& { + return name_lookup_; + } + + protected: + llvm::SmallVector nodes_; + llvm::StringMap name_lookup_; +}; + +using DeclarationBlock = MetaNodeBlock; +using StatementBlock = MetaNodeBlock; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_META_NODE_BLOCK_H_ diff --git a/toolchain/semantics/nodes/declared_name.h b/toolchain/semantics/nodes/declared_name.h new file mode 100644 index 000000000000..ac924f4fe0fd --- /dev/null +++ b/toolchain/semantics/nodes/declared_name.h @@ -0,0 +1,26 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" + +namespace Carbon::Semantics { + +// Represents a name. +class DeclaredName { + public: + explicit DeclaredName(ParseTree::Node node) : node_(node) {} + + auto node() const -> ParseTree::Node { return node_; } + + private: + ParseTree::Node node_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_H_ diff --git a/toolchain/semantics/nodes/declared_name_test_matchers.h b/toolchain/semantics/nodes/declared_name_test_matchers.h new file mode 100644 index 000000000000..a598536e43d8 --- /dev/null +++ b/toolchain/semantics/nodes/declared_name_test_matchers.h @@ -0,0 +1,28 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_TEST_MATCHERS_H_ + +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/declared_name.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +MATCHER_P( + DeclaredName, name_matcher, + llvm::formatv("DeclaredName {0}", + ::testing::DescribeMatcher(name_matcher))) { + const Semantics::DeclaredName& name = arg; + return ExplainMatchResult(name_matcher, + SemanticsIRForTest::GetNodeText(name.node()), + result_listener); +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_DECLARED_NAME_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/nodes/expression_statement.h b/toolchain/semantics/nodes/expression_statement.h new file mode 100644 index 000000000000..f092268ec77b --- /dev/null +++ b/toolchain/semantics/nodes/expression_statement.h @@ -0,0 +1,31 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_EXPRESSION_STATEMENT_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_EXPRESSION_STATEMENT_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" + +namespace Carbon::Semantics { + +// Represents a statement that is only an expression, such as `Call()`. +class ExpressionStatement { + public: + static constexpr StatementKind MetaNodeKind = + StatementKind::ExpressionStatement; + + explicit ExpressionStatement(Expression expression) + : expression_(expression) {} + + auto expression() const -> Expression { return expression_; } + + private: + Expression expression_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_EXPRESSION_STATEMENT_H_ diff --git a/toolchain/semantics/nodes/function.h b/toolchain/semantics/nodes/function.h new file mode 100644 index 000000000000..d303912975f3 --- /dev/null +++ b/toolchain/semantics/nodes/function.h @@ -0,0 +1,60 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_H_ + +#include "common/ostream.h" +#include "llvm/ADT/SmallVector.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" +#include "toolchain/semantics/meta_node_block.h" +#include "toolchain/semantics/nodes/declared_name.h" +#include "toolchain/semantics/nodes/pattern_binding.h" + +namespace Carbon::Semantics { + +// Represents `fn name(params...) [-> return_expr] body`. +class Function { + public: + static constexpr DeclarationKind MetaNodeKind = DeclarationKind::Function; + + Function(ParseTree::Node node, DeclaredName name, + llvm::SmallVector params, + llvm::Optional return_expr, + StatementBlock body) + : node_(node), + name_(name), + params_(std::move(params)), + return_expr_(return_expr), + body_(std::move(body)) {} + + auto node() const -> ParseTree::Node { return node_; } + auto name() const -> const DeclaredName& { return name_; } + auto params() const -> llvm::ArrayRef { return params_; } + auto return_expr() const -> llvm::Optional { + return return_expr_; + } + + auto body() const -> const StatementBlock& { return body_; } + + private: + // The FunctionDeclaration node. + ParseTree::Node node_; + + // The function's name. + DeclaredName name_; + + // Regular function parameters. + llvm::SmallVector params_; + + // The return expression. + llvm::Optional return_expr_; + + StatementBlock body_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_H_ diff --git a/toolchain/semantics/nodes/function_test_matchers.h b/toolchain/semantics/nodes/function_test_matchers.h new file mode 100644 index 000000000000..3d17371831be --- /dev/null +++ b/toolchain/semantics/nodes/function_test_matchers.h @@ -0,0 +1,63 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_TEST_MATCHERS_H_ + +#include +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/function.h" +#include "toolchain/semantics/nodes/pattern_binding.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +MATCHER_P(FunctionName, name_matcher, + llvm::formatv("fn `{0}`", ::testing::DescribeMatcher( + name_matcher))) { + const Semantics::Declaration& decl = arg; + if (auto function = + SemanticsIRForTest::GetDeclaration(decl)) { + return ExplainMatchResult( + name_matcher, SemanticsIRForTest::GetNodeText(function->name().node()), + result_listener); + } else { + *result_listener << "node is not a function"; + return result_listener; + } +} + +MATCHER_P4( + Function, name_matcher, param_matcher, return_matcher, body_matcher, + llvm::formatv( + "fn `{0}` params `{1}` returns `{2}` body `{3}`", + ::testing::DescribeMatcher(name_matcher), + ::testing::DescribeMatcher>( + param_matcher), + ::testing::DescribeMatcher>( + return_matcher), + ::testing::DescribeMatcher(body_matcher))) { + const Semantics::Declaration& decl = arg; + if (auto function = + SemanticsIRForTest::GetDeclaration(decl)) { + return ExplainMatchResult( + name_matcher, + SemanticsIRForTest::GetNodeText(function->name().node()), + result_listener) && + ExplainMatchResult(param_matcher, function->params(), + result_listener) && + ExplainMatchResult(return_matcher, function->return_expr(), + result_listener) && + ExplainMatchResult(body_matcher, function->body(), result_listener); + } else { + *result_listener << "node is not a function"; + return result_listener; + } +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/nodes/infix_operator.h b/toolchain/semantics/nodes/infix_operator.h new file mode 100644 index 000000000000..09862d241669 --- /dev/null +++ b/toolchain/semantics/nodes/infix_operator.h @@ -0,0 +1,34 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" + +namespace Carbon::Semantics { + +// Represents an infix operator, such as `+` in `1 + 2`. +class InfixOperator { + public: + static constexpr ExpressionKind MetaNodeKind = ExpressionKind::InfixOperator; + + explicit InfixOperator(ParseTree::Node node, Expression lhs, Expression rhs) + : node_(node), lhs_(lhs), rhs_(rhs) {} + + auto node() const -> ParseTree::Node { return node_; } + auto lhs() const -> Expression { return lhs_; } + auto rhs() const -> Expression { return rhs_; } + + private: + ParseTree::Node node_; + Expression lhs_; + Expression rhs_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_H_ diff --git a/toolchain/semantics/nodes/infix_operator_test_matchers.h b/toolchain/semantics/nodes/infix_operator_test_matchers.h new file mode 100644 index 000000000000..f60c9110bb7f --- /dev/null +++ b/toolchain/semantics/nodes/infix_operator_test_matchers.h @@ -0,0 +1,39 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_TEST_MATCHERS_H_ + +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/infix_operator.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +MATCHER_P3( + InfixOperator, lhs_matcher, op_matcher, rhs_matcher, + llvm::formatv( + "InfixOperator {0} {1} {2}", + ::testing::DescribeMatcher(lhs_matcher), + ::testing::DescribeMatcher(op_matcher), + ::testing::DescribeMatcher(rhs_matcher))) { + const Semantics::Expression& expr = arg; + if (auto infix = + SemanticsIRForTest::GetExpression(expr)) { + return ExplainMatchResult(op_matcher, + SemanticsIRForTest::GetNodeText(infix->node()), + result_listener) && + ExplainMatchResult(lhs_matcher, infix->lhs(), result_listener) && + ExplainMatchResult(rhs_matcher, infix->rhs(), result_listener); + } else { + *result_listener << "node is not a literal"; + return result_listener; + } +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_INFIX_OPERATOR_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/nodes/literal.h b/toolchain/semantics/nodes/literal.h new file mode 100644 index 000000000000..258e91e5dd02 --- /dev/null +++ b/toolchain/semantics/nodes/literal.h @@ -0,0 +1,29 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" + +namespace Carbon::Semantics { + +// Represents all kinds of literals: `1`, `i32`, etc. +class Literal { + public: + static constexpr ExpressionKind MetaNodeKind = ExpressionKind::Literal; + + explicit Literal(ParseTree::Node node) : node_(node) {} + + auto node() const -> ParseTree::Node { return node_; } + + private: + ParseTree::Node node_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_H_ diff --git a/toolchain/semantics/nodes/literal_test_matchers.h b/toolchain/semantics/nodes/literal_test_matchers.h new file mode 100644 index 000000000000..78f88a9599bb --- /dev/null +++ b/toolchain/semantics/nodes/literal_test_matchers.h @@ -0,0 +1,33 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_TEST_MATCHERS_H_ + +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/literal.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +MATCHER_P( + Literal, text_matcher, + llvm::formatv("Literal {0}", + ::testing::DescribeMatcher(text_matcher))) { + const Semantics::Expression& expr = arg; + if (auto lit = SemanticsIRForTest::GetExpression(expr)) { + return ExplainMatchResult(text_matcher, + SemanticsIRForTest::GetNodeText(lit->node()), + result_listener); + } else { + *result_listener << "node is not a literal"; + return result_listener; + } +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_LITERAL_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/nodes/pattern_binding.h b/toolchain/semantics/nodes/pattern_binding.h new file mode 100644 index 000000000000..1515fe6705d2 --- /dev/null +++ b/toolchain/semantics/nodes/pattern_binding.h @@ -0,0 +1,33 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" +#include "toolchain/semantics/nodes/declared_name.h" + +namespace Carbon::Semantics { + +// Represents `name: type`. +class PatternBinding { + public: + PatternBinding(ParseTree::Node node, DeclaredName name, Expression type) + : node_(node), name_(name), type_(type) {} + + auto node() const -> ParseTree::Node { return node_; } + auto name() const -> const DeclaredName& { return name_; } + auto type() const -> const Expression& { return type_; } + + private: + ParseTree::Node node_; + DeclaredName name_; + Expression type_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_H_ diff --git a/toolchain/semantics/nodes/pattern_binding_test_matchers.h b/toolchain/semantics/nodes/pattern_binding_test_matchers.h new file mode 100644 index 000000000000..40e343cad5a8 --- /dev/null +++ b/toolchain/semantics/nodes/pattern_binding_test_matchers.h @@ -0,0 +1,30 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_TEST_MATCHERS_H_ + +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/declared_name_test_matchers.h" +#include "toolchain/semantics/nodes/pattern_binding.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +inline auto PatternBinding( + ::testing::Matcher name_matcher, + ::testing::Matcher type_matcher) + -> ::testing::Matcher { + return ::testing::AllOf( + ::testing::Property("name", &Semantics::PatternBinding::name, + DeclaredName(name_matcher)), + ::testing::Property("type", &Semantics::PatternBinding::type, + type_matcher)); +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_PATTERN_BINDING_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/nodes/return.h b/toolchain/semantics/nodes/return.h new file mode 100644 index 000000000000..c66cabc26060 --- /dev/null +++ b/toolchain/semantics/nodes/return.h @@ -0,0 +1,32 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_H_ + +#include "common/ostream.h" +#include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/meta_node.h" + +namespace Carbon::Semantics { + +// Represents `return [expr];` +class Return { + public: + static constexpr StatementKind MetaNodeKind = StatementKind::Return; + + Return(ParseTree::Node node, llvm::Optional expr) + : node_(node), expr_(expr) {} + + auto node() const -> ParseTree::Node { return node_; } + auto expression() const -> const llvm::Optional& { return expr_; } + + private: + ParseTree::Node node_; + llvm::Optional expr_; +}; + +} // namespace Carbon::Semantics + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_H_ diff --git a/toolchain/semantics/nodes/return_test_matchers.h b/toolchain/semantics/nodes/return_test_matchers.h new file mode 100644 index 000000000000..6d88819f6f3c --- /dev/null +++ b/toolchain/semantics/nodes/return_test_matchers.h @@ -0,0 +1,32 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_TEST_MATCHERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_TEST_MATCHERS_H_ + +#include + +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/return.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +MATCHER_P(Return, expr_matcher, + llvm::formatv( + "Return {0}", + ::testing::DescribeMatcher>( + expr_matcher))) { + const Semantics::Statement& stmt = arg; + if (auto ret = SemanticsIRForTest::GetStatement(stmt)) { + return ExplainMatchResult(expr_matcher, ret->expression(), result_listener); + } else { + *result_listener << "node is not a function"; + return result_listener; + } +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_TEST_MATCHERS_H_ diff --git a/toolchain/semantics/parse_subtree_consumer.cpp b/toolchain/semantics/parse_subtree_consumer.cpp new file mode 100644 index 000000000000..6d347a004e87 --- /dev/null +++ b/toolchain/semantics/parse_subtree_consumer.cpp @@ -0,0 +1,66 @@ +// 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 "toolchain/semantics/parse_subtree_consumer.h" + +#include "common/check.h" + +namespace Carbon { + +auto ParseSubtreeConsumer::ForParent(const ParseTree& parse_tree, + ParseTree::Node parent_node) + -> ParseSubtreeConsumer { + auto range = llvm::reverse(parse_tree.postorder(parent_node)); + // The cursor should be one after the parent. + return ParseSubtreeConsumer(parse_tree, ++range.begin(), range.end()); +} + +auto ParseSubtreeConsumer::ForTree(const ParseTree& parse_tree) + -> ParseSubtreeConsumer { + auto range = llvm::reverse(parse_tree.postorder()); + return ParseSubtreeConsumer(parse_tree, range.begin(), range.end()); +} + +ParseSubtreeConsumer::~ParseSubtreeConsumer() { + CARBON_CHECK(is_done()) << "At index " << (*cursor_).index() << ", unhandled " + << parse_tree_->node_kind(*cursor_); +} + +auto ParseSubtreeConsumer::RequireConsume() -> ParseTree::Node { + CARBON_CHECK(!is_done()) << "Done with subtree, expected more"; + return GetNodeAndAdvance(); +} + +auto ParseSubtreeConsumer::RequireConsume(ParseNodeKind node_kind) + -> ParseTree::Node { + CARBON_CHECK(!is_done()) << "Done with subtree, expected " << node_kind; + auto node = GetNodeAndAdvance(); + CARBON_CHECK(node_kind == parse_tree_->node_kind(node)) + << "At index " << node.index() << ", expected " << node_kind << ", found " + << parse_tree_->node_kind(node); + return node; +} + +auto ParseSubtreeConsumer::TryConsume() -> llvm::Optional { + if (is_done()) { + return llvm::None; + } + return GetNodeAndAdvance(); +} + +auto ParseSubtreeConsumer::TryConsume(ParseNodeKind node_kind) + -> llvm::Optional { + if (is_done() || node_kind != parse_tree_->node_kind(*cursor_)) { + return llvm::None; + } + return GetNodeAndAdvance(); +} + +auto ParseSubtreeConsumer::GetNodeAndAdvance() -> ParseTree::Node { + auto node = *cursor_; + cursor_ += parse_tree_->node_subtree_size(node); + return node; +} + +} // namespace Carbon diff --git a/toolchain/semantics/parse_subtree_consumer.h b/toolchain/semantics/parse_subtree_consumer.h new file mode 100644 index 000000000000..2ea8fc7363aa --- /dev/null +++ b/toolchain/semantics/parse_subtree_consumer.h @@ -0,0 +1,67 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_PARSE_SUBTREE_CONSUMER_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_PARSE_SUBTREE_CONSUMER_H_ + +#include "toolchain/parser/parse_tree.h" + +namespace Carbon { + +// Consumes a subtree from the parser, returning only its direct children. +// +// This traverses in reverse postorder because the parent of a subtree needs to +// be seen before its children. +class ParseSubtreeConsumer { + public: + using ParseTreeIterator = std::reverse_iterator; + + // Returns a subtree consumer for a particular node in the tree. + static auto ForParent(const ParseTree& parse_tree, + ParseTree::Node parent_node) -> ParseSubtreeConsumer; + + // Returns a subtree consumer for the root of the tree. + static auto ForTree(const ParseTree& parse_tree) -> ParseSubtreeConsumer; + + // Prevent copies because we require completion of parsing in the destructor. + ParseSubtreeConsumer(const ParseSubtreeConsumer&) = delete; + auto operator=(const ParseSubtreeConsumer&) -> ParseSubtreeConsumer& = delete; + + ~ParseSubtreeConsumer(); + + // Returns the next node. + // CHECK-fails on unexpected states. + [[nodiscard]] auto RequireConsume() -> ParseTree::Node; + + // Requires the next node be of the given kind, and returns it. + // CHECK-fails on unexpected states. + [[nodiscard]] auto RequireConsume(ParseNodeKind node_kind) -> ParseTree::Node; + + // Returns the next node if one exists. + [[nodiscard]] auto TryConsume() -> llvm::Optional; + + // Returns the next node if it's of the given kind. + [[nodiscard]] auto TryConsume(ParseNodeKind node_kind) + -> llvm::Optional; + + // Returns true if there are no more nodes to consume. + auto is_done() -> bool { return cursor_ == subtree_end_; } + + private: + // Constructs for a subtree. + ParseSubtreeConsumer(const ParseTree& parse_tree, ParseTreeIterator cursor, + ParseTreeIterator subtree_end) + : parse_tree_(&parse_tree), cursor_(cursor), subtree_end_(subtree_end) {} + + // Advances to the next sibling, returning the current node. + auto GetNodeAndAdvance() -> ParseTree::Node; + + const ParseTree* parse_tree_; + ParseTreeIterator cursor_; + ParseTreeIterator subtree_end_; +}; + +} // namespace Carbon + +#endif // CARBON_TOOLCHAIN_SEMANTICS_PARSE_SUBTREE_CONSUMER_H_ diff --git a/toolchain/semantics/semantics_ir.cpp b/toolchain/semantics/semantics_ir.cpp index 13c837aa10a6..da33b1144cc9 100644 --- a/toolchain/semantics/semantics_ir.cpp +++ b/toolchain/semantics/semantics_ir.cpp @@ -7,22 +7,120 @@ #include "common/check.h" #include "llvm/Support/FormatVariadic.h" #include "toolchain/lexer/tokenized_buffer.h" +#include "toolchain/semantics/nodes/expression_statement.h" namespace Carbon { -void SemanticsIR::Block::Add(llvm::StringRef name, Node named_entity) { - ordering_.push_back(named_entity); - name_lookup_.insert({name, named_entity}); +void SemanticsIR::Print(llvm::raw_ostream& out, ParseTree::Node node) const { + out << parse_tree_->GetNodeText(node); } -auto SemanticsIR::AddFunction(Block& block, ParseTree::Node decl_node, - ParseTree::Node name_node) - -> Semantics::Function& { - int32_t index = functions_.size(); - functions_.push_back(Semantics::Function(decl_node, name_node)); - block.Add(parse_tree_->GetNodeText(name_node), - Node(Node::Kind::Function, index)); - return functions_[index]; +void SemanticsIR::Print(llvm::raw_ostream& out, + Semantics::Declaration decl) const { + switch (decl.kind()) { + case Semantics::DeclarationKind::Function: + Print(out, declarations_.Get(decl)); + return; + case Semantics::DeclarationKind::Invalid: + CARBON_FATAL() << "Invalid declaration type"; + } +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + Semantics::Expression expr) const { + switch (expr.kind()) { + case Semantics::ExpressionKind::InfixOperator: + Print(out, expressions_.Get(expr)); + return; + case Semantics::ExpressionKind::Literal: + Print(out, expressions_.Get(expr)); + return; + case Semantics::ExpressionKind::Invalid: + CARBON_FATAL() << "Invalid expression type"; + } +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + Semantics::Statement stmt) const { + switch (stmt.kind()) { + case Semantics::StatementKind::ExpressionStatement: + Print(out, statements_.Get(stmt)); + return; + case Semantics::StatementKind::Return: + Print(out, statements_.Get(stmt)); + return; + case Semantics::StatementKind::Invalid: + CARBON_FATAL() << "Invalid expression type"; + } + out << ";"; +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::DeclaredName& name) const { + Print(out, name.node()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::ExpressionStatement& expr) const { + Print(out, expr.expression()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::Function& function) const { + out << "fn "; + Print(out, function.name()); + out << "("; + llvm::ListSeparator sep; + for (const auto& param : function.params()) { + out << sep; + Print(out, param); + } + out << ")"; + if (function.return_expr()) { + out << " -> "; + Print(out, *function.return_expr()); + } + Print(out, function.body()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::InfixOperator& op) const { + Print(out, op.lhs()); + out << " "; + Print(out, op.node()); + out << " "; + Print(out, op.rhs()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::Literal& literal) const { + Print(out, literal.node()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::PatternBinding& binding) const { + Print(out, binding.name()); + out << ": "; + Print(out, binding.type()); +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::Return& ret) const { + out << "return"; + if (ret.expression()) { + out << " "; + Print(out, *ret.expression()); + } +} + +void SemanticsIR::Print(llvm::raw_ostream& out, + const Semantics::StatementBlock& block) const { + out << " { "; + for (const auto& statement : block.nodes()) { + Print(out, statement); + out << "; "; + } + out << "}"; } } // namespace Carbon diff --git a/toolchain/semantics/semantics_ir.h b/toolchain/semantics/semantics_ir.h index 1c618db5cd9d..be77052b6a24 100644 --- a/toolchain/semantics/semantics_ir.h +++ b/toolchain/semantics/semantics_ir.h @@ -6,69 +6,63 @@ #define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_H_ #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" #include "toolchain/parser/parse_tree.h" -#include "toolchain/semantics/function.h" +#include "toolchain/semantics/meta_node_block.h" +#include "toolchain/semantics/nodes/expression_statement.h" +#include "toolchain/semantics/nodes/function.h" +#include "toolchain/semantics/nodes/infix_operator.h" +#include "toolchain/semantics/nodes/literal.h" +#include "toolchain/semantics/nodes/pattern_binding.h" +#include "toolchain/semantics/nodes/return.h" + +namespace Carbon::Testing { +class SemanticsIRForTest; +} // namespace Carbon::Testing namespace Carbon { // Provides semantic analysis on a ParseTree. class SemanticsIR { public: - // Provides a link back to a semantic node in a name scope. - class Node { - public: - Node() : Node(Kind::Invalid, -1) {} + // File-level declarations. + auto root_block() const -> const Semantics::DeclarationBlock& { + return *root_block_; + } - private: - friend class SemanticsIR; + // Debug printer for the parse tree. + void Print(llvm::raw_ostream& out, ParseTree::Node node) const; - // The kind of token. These correspond to the lists on SemanticsIR which - // will be indexed into. - enum class Kind { - Invalid, - Function, - }; + // Debug printers for meta nodes. + void Print(llvm::raw_ostream& out, Semantics::Declaration decl) const; + void Print(llvm::raw_ostream& out, Semantics::Expression expr) const; + void Print(llvm::raw_ostream& out, Semantics::Statement stmt) const; - Node(Kind kind, int32_t index) : kind_(kind), index_(index) { - // TODO: kind_ and index_ are currently unused, this suppresses the - // warning. - kind_ = kind; - index_ = index; - } - - Kind kind_; - - // The index of the named entity within its list. - int32_t index_; - }; - - struct Block { - public: - void Add(llvm::StringRef name, Node named_entity); - - private: - llvm::SmallVector ordering_; - llvm::StringMap name_lookup_; - }; + // Debug printers for other nodes. + void Print(llvm::raw_ostream& out, const Semantics::DeclaredName& name) const; + void Print(llvm::raw_ostream& out, + const Semantics::ExpressionStatement& expr) const; + void Print(llvm::raw_ostream& out, const Semantics::Function& function) const; + void Print(llvm::raw_ostream& out, const Semantics::InfixOperator& op) const; + void Print(llvm::raw_ostream& out, const Semantics::Literal& literal) const; + void Print(llvm::raw_ostream& out, + const Semantics::PatternBinding& binding) const; + void Print(llvm::raw_ostream& out, const Semantics::Return& ret) const; + void Print(llvm::raw_ostream& out, + const Semantics::StatementBlock& block) const; private: friend class SemanticsIRFactory; + friend class Testing::SemanticsIRForTest; explicit SemanticsIR(const ParseTree& parse_tree) : parse_tree_(&parse_tree) {} - // Creates a function, adds it to the enclosing scope, and returns a reference - // for further mutations. On a name collision, it will not be added to the - // scope, but will still be returned. - auto AddFunction(Block& block, ParseTree::Node decl_node, - ParseTree::Node name_node) -> Semantics::Function&; + Semantics::DeclarationStore declarations_; + Semantics::ExpressionStore expressions_; + Semantics::StatementStore statements_; - // Indexed by Token::Function. - llvm::SmallVector functions_; - - // The file-level block. - Block root_block_; + // The file-level block. Only assigned after initialization is complete. + llvm::Optional root_block_; const ParseTree* parse_tree_; }; diff --git a/toolchain/semantics/semantics_ir_factory.cpp b/toolchain/semantics/semantics_ir_factory.cpp index 8a8f4d61d12b..9f7e0406894a 100644 --- a/toolchain/semantics/semantics_ir_factory.cpp +++ b/toolchain/semantics/semantics_ir_factory.cpp @@ -4,55 +4,218 @@ #include "toolchain/semantics/semantics_ir_factory.h" +#include + #include "common/check.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/FormatVariadic.h" #include "toolchain/lexer/tokenized_buffer.h" #include "toolchain/parser/parse_node_kind.h" +#include "toolchain/semantics/meta_node_block.h" +#include "toolchain/semantics/nodes/expression_statement.h" +#include "toolchain/semantics/parse_subtree_consumer.h" namespace Carbon { +// The ParseTree is walked in reverse post order, meaning a lot of nodes are +// added in reverse. This fixes that ordering to be the easier to understand +// code ordering. +template +static void FixReverseOrdering(T& container) { + std::reverse(container.begin(), container.end()); +} + auto SemanticsIRFactory::Build(const ParseTree& parse_tree) -> SemanticsIR { SemanticsIRFactory builder(parse_tree); - builder.ProcessRoots(); + builder.Build(); return builder.semantics_; } -void SemanticsIRFactory::ProcessRoots() { - for (ParseTree::Node node : semantics_.parse_tree_->roots()) { - switch (semantics_.parse_tree_->node_kind(node)) { - case ParseNodeKind::FunctionDeclaration(): - ProcessFunctionNode(semantics_.root_block_, node); +void SemanticsIRFactory::Build() { + auto subtree = ParseSubtreeConsumer::ForTree(parse_tree()); + // FileEnd is a placeholder node which can be discarded. + RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::FileEnd())); + llvm::SmallVector nodes; + llvm::StringMap name_lookup; + while (llvm::Optional node = subtree.TryConsume()) { + switch (auto node_kind = parse_tree().node_kind(*node)) { + case ParseNodeKind::FunctionDeclaration(): { + auto [name, decl] = TransformFunctionDeclaration(*node); + nodes.push_back(decl); + name_lookup[name] = decl; break; - case ParseNodeKind::FileEnd(): - // No action needed. + } + default: + CARBON_FATAL() << "At index " << node->index() << ", unexpected " + << node_kind; + } + } + FixReverseOrdering(nodes); + semantics_.root_block_ = + Semantics::DeclarationBlock(std::move(nodes), std::move(name_lookup)); +} + +void SemanticsIRFactory::RequireNodeEmpty(ParseTree::Node node) { + auto subtree_size = parse_tree().node_subtree_size(node); + CARBON_CHECK(subtree_size == 1) + << "At index " << node.index() << ", expected " + << parse_tree().node_kind(node) + << "would have subtree_size of 1, but was " << subtree_size; +} + +auto SemanticsIRFactory::TransformCodeBlock(ParseTree::Node node) + -> Semantics::StatementBlock { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::CodeBlock()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::CodeBlockEnd())); + + llvm::SmallVector nodes; + while (llvm::Optional child = subtree.TryConsume()) { + switch (auto child_kind = parse_tree().node_kind(*child)) { + case ParseNodeKind::ExpressionStatement(): + nodes.push_back(TransformExpressionStatement(*child)); + break; + case ParseNodeKind::ReturnStatement(): + nodes.push_back(TransformReturnStatement(*child)); + break; + case ParseNodeKind::VariableDeclaration(): + // TODO: Handle. break; default: - CARBON_FATAL() << "Unhandled node kind: " - << semantics_.parse_tree_->node_kind(node).name(); + CARBON_FATAL() << "At index " << child->index() << ", unexpected " + << child_kind; } } + FixReverseOrdering(nodes); + return Semantics::StatementBlock(std::move(nodes), + /*name_lookup=*/{}); +} + +auto SemanticsIRFactory::TransformDeclaredName(ParseTree::Node node) + -> Semantics::DeclaredName { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::DeclaredName()); + RequireNodeEmpty(node); + + return Semantics::DeclaredName(node); +} + +auto SemanticsIRFactory::TransformExpression(ParseTree::Node node) + -> Semantics::Expression { + switch (auto node_kind = parse_tree().node_kind(node)) { + case ParseNodeKind::Literal(): + RequireNodeEmpty(node); + return semantics_.expressions_.Store(Semantics::Literal(node)); + case ParseNodeKind::InfixOperator(): + return semantics_.expressions_.Store(TransformInfixOperator(node)); + default: + CARBON_FATAL() << "At index " << node.index() << ", unexpected " + << node_kind; + break; + } } -void SemanticsIRFactory::ProcessFunctionNode(SemanticsIR::Block& block, - ParseTree::Node decl_node) { - llvm::Optional fn; - for (ParseTree::Node node : semantics_.parse_tree_->children(decl_node)) { - switch (semantics_.parse_tree_->node_kind(node)) { - case ParseNodeKind::DeclaredName(): - fn = semantics_.AddFunction(block, decl_node, node); - break; - case ParseNodeKind::ParameterList(): - // TODO: Maybe something like Semantics::AddVariable passed to - // Function::AddParameter. - break; - case ParseNodeKind::CodeBlock(): - // TODO: Should accumulate the definition into the code block. - break; - default: - CARBON_FATAL() << "Unhandled node kind: " - << semantics_.parse_tree_->node_kind(node).name(); +auto SemanticsIRFactory::TransformExpressionStatement(ParseTree::Node node) + -> Semantics::Statement { + CARBON_CHECK(parse_tree().node_kind(node) == + ParseNodeKind::ExpressionStatement()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::StatementEnd())); + return semantics_.statements_.Store(Semantics::ExpressionStatement( + TransformExpression(subtree.RequireConsume()))); +} + +auto SemanticsIRFactory::TransformFunctionDeclaration(ParseTree::Node node) + -> std::tuple { + CARBON_CHECK(parse_tree().node_kind(node) == + ParseNodeKind::FunctionDeclaration()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + auto body = + TransformCodeBlock(subtree.RequireConsume(ParseNodeKind::CodeBlock())); + llvm::Optional return_type_expr; + if (auto return_type_node = subtree.TryConsume(ParseNodeKind::ReturnType())) { + return_type_expr = TransformReturnType(*return_type_node); + } + auto params = TransformParameterList( + subtree.RequireConsume(ParseNodeKind::ParameterList())); + auto name = TransformDeclaredName( + subtree.RequireConsume(ParseNodeKind::DeclaredName())); + auto decl = semantics_.declarations_.Store( + Semantics::Function(node, name, params, return_type_expr, body)); + return std::make_tuple(parse_tree().GetNodeText(name.node()), decl); +} + +auto SemanticsIRFactory::TransformInfixOperator(ParseTree::Node node) + -> Semantics::InfixOperator { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::InfixOperator()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + auto rhs = TransformExpression(subtree.RequireConsume()); + auto lhs = TransformExpression(subtree.RequireConsume()); + return Semantics::InfixOperator(node, lhs, rhs); +} + +auto SemanticsIRFactory::TransformParameterList(ParseTree::Node node) + -> llvm::SmallVector { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::ParameterList()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::ParameterListEnd())); + + llvm::SmallVector params; + if (auto first_param_node = + subtree.TryConsume(ParseNodeKind::PatternBinding())) { + params.push_back(TransformPatternBinding(*first_param_node)); + + while (auto comma_node = + subtree.TryConsume(ParseNodeKind::ParameterListComma())) { + RequireNodeEmpty(*comma_node); + params.push_back(TransformPatternBinding( + subtree.RequireConsume(ParseNodeKind::PatternBinding()))); } } + FixReverseOrdering(params); + return params; +} + +auto SemanticsIRFactory::TransformPatternBinding(ParseTree::Node node) + -> Semantics::PatternBinding { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::PatternBinding()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + auto type = TransformExpression(subtree.RequireConsume()); + auto name = TransformDeclaredName( + subtree.RequireConsume(ParseNodeKind::DeclaredName())); + return Semantics::PatternBinding(node, name, type); +} + +auto SemanticsIRFactory::TransformReturnStatement(ParseTree::Node node) + -> Semantics::Statement { + CARBON_CHECK(parse_tree().node_kind(node) == + ParseNodeKind::ReturnStatement()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::StatementEnd())); + + auto expr = subtree.TryConsume(); + if (expr) { + // return expr; + return semantics_.statements_.Store( + Semantics::Return(node, TransformExpression(*expr))); + } else { + // return; + return semantics_.statements_.Store(Semantics::Return(node, llvm::None)); + } +} + +auto SemanticsIRFactory::TransformReturnType(ParseTree::Node node) + -> Semantics::Expression { + CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::ReturnType()); + + auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node); + return TransformExpression(subtree.RequireConsume()); } } // namespace Carbon diff --git a/toolchain/semantics/semantics_ir_factory.h b/toolchain/semantics/semantics_ir_factory.h index 7c23141917b9..3ff588f32512 100644 --- a/toolchain/semantics/semantics_ir_factory.h +++ b/toolchain/semantics/semantics_ir_factory.h @@ -5,9 +5,6 @@ #ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FACTORY_H_ #define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FACTORY_H_ -#include - -#include "llvm/ADT/StringMap.h" #include "toolchain/parser/parse_tree.h" #include "toolchain/semantics/semantics_ir.h" @@ -23,14 +20,31 @@ class SemanticsIRFactory { explicit SemanticsIRFactory(const ParseTree& parse_tree) : semantics_(parse_tree) {} - // Processes the roots of the ParseTree into semantics_, transitively - // handling children. - void ProcessRoots(); + void Build(); - // Turns a function node from the parse tree into a semantic function node, - // adding it to the containing scope. - void ProcessFunctionNode(SemanticsIR::Block& block, - ParseTree::Node decl_node); + // Requires that a node have no children, to emphasize why the subtree isn't + // otherwise checked. + void RequireNodeEmpty(ParseTree::Node node); + + // Each of these takes a parse tree node and does a transformation based on + // its type. These functions are per ParseNodeKind. + auto TransformCodeBlock(ParseTree::Node node) -> Semantics::StatementBlock; + auto TransformDeclaredName(ParseTree::Node node) -> Semantics::DeclaredName; + auto TransformExpression(ParseTree::Node node) -> Semantics::Expression; + auto TransformExpressionStatement(ParseTree::Node node) + -> Semantics::Statement; + auto TransformFunctionDeclaration(ParseTree::Node node) + -> std::tuple; + auto TransformInfixOperator(ParseTree::Node node) -> Semantics::InfixOperator; + auto TransformParameterList(ParseTree::Node node) + -> llvm::SmallVector; + auto TransformPatternBinding(ParseTree::Node node) + -> Semantics::PatternBinding; + auto TransformReturnType(ParseTree::Node node) -> Semantics::Expression; + auto TransformReturnStatement(ParseTree::Node node) -> Semantics::Statement; + + // Convenience accessor. + auto parse_tree() -> const ParseTree& { return *semantics_.parse_tree_; } SemanticsIR semantics_; }; diff --git a/toolchain/semantics/semantics_ir_factory_test.cpp b/toolchain/semantics/semantics_ir_factory_test.cpp index bb470469c4cb..e4e539b38c70 100644 --- a/toolchain/semantics/semantics_ir_factory_test.cpp +++ b/toolchain/semantics/semantics_ir_factory_test.cpp @@ -7,49 +7,170 @@ #include #include -#include - #include "toolchain/diagnostics/mocks.h" #include "toolchain/lexer/tokenized_buffer.h" #include "toolchain/parser/parse_tree.h" +#include "toolchain/semantics/semantics_ir_test_helpers.h" #include "toolchain/source/source_buffer.h" namespace Carbon::Testing { namespace { using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Optional; +using ::testing::UnorderedElementsAre; class SemanticsIRFactoryTest : public ::testing::Test { protected: - auto Analyze(llvm::Twine t) -> SemanticsIR { - source_buffer.emplace(std::move(*SourceBuffer::CreateFromText(t.str()))); + void Build(llvm::Twine t) { + source_buffer.emplace(std::move(*SourceBuffer::CreateFromText(t))); tokenized_buffer = TokenizedBuffer::Lex(*source_buffer, consumer); EXPECT_FALSE(tokenized_buffer->has_errors()); parse_tree = ParseTree::Parse(*tokenized_buffer, consumer); EXPECT_FALSE(parse_tree->has_errors()); - return SemanticsIRFactory::Build(*parse_tree); + SemanticsIRForTest::set_semantics(SemanticsIRFactory::Build(*parse_tree)); } - std::optional source_buffer; - std::optional tokenized_buffer; - std::optional parse_tree; + ~SemanticsIRFactoryTest() override { SemanticsIRForTest::clear(); } + + void ExpectRootBlock( + ::testing::Matcher> decls, + ::testing::Matcher> name_lookup) { + EXPECT_THAT(SemanticsIRForTest::semantics().root_block().nodes(), decls); + EXPECT_THAT(SemanticsIRForTest::semantics().root_block().name_lookup(), + name_lookup); + } + + llvm::Optional source_buffer; + llvm::Optional tokenized_buffer; + llvm::Optional parse_tree; MockDiagnosticConsumer consumer; }; +/* +TEST_F(SemanticsIRFactoryTest, SimpleProgram) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(// package FactoryTest api; + + fn Add(x: i32, y: i32) -> i32 { + return x + y; + } + + fn Main() -> i32 { + var x: i32 = Add(3, 10); + x *= 5; + return x; + } + )"); + ExpectRootBlock( + ElementsAre( + Function( + Eq("Add"), + ElementsAre(PatternBinding(Eq("x"), Literal("i32")), + PatternBinding(Eq("y"), Literal("i32"))), + Optional(Literal("i32"))), + Function(Eq("Main"), IsEmpty(), Optional(Literal("i32")))), + UnorderedElementsAre(MappedNode("Add", FunctionName("Add")), + MappedNode("Main", FunctionName("Main")))); +} +*/ + TEST_F(SemanticsIRFactoryTest, Empty) { EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); - Analyze(""); + Build(""); + ExpectRootBlock(IsEmpty(), IsEmpty()); } TEST_F(SemanticsIRFactoryTest, FunctionBasic) { EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); - Analyze("fn Foo() {}"); + Build("fn Foo() {}"); + ExpectRootBlock(ElementsAre(Function(Eq("Foo"), IsEmpty(), IsNone(), + StatementBlock(IsEmpty(), IsEmpty()))), + UnorderedElementsAre(MappedNode("Foo", FunctionName("Foo")))); +} + +TEST_F(SemanticsIRFactoryTest, FunctionParams) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build("fn Foo(x: i32, y: i64) {}"); + ExpectRootBlock( + ElementsAre(Function(Eq("Foo"), + ElementsAre(PatternBinding(Eq("x"), Literal("i32")), + PatternBinding(Eq("y"), Literal("i64"))), + IsNone(), StatementBlock(IsEmpty(), IsEmpty()))), + UnorderedElementsAre(MappedNode("Foo", FunctionName("Foo")))); +} + +TEST_F(SemanticsIRFactoryTest, FunctionReturnType) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build("fn Foo() -> i32 {}"); + ExpectRootBlock( + ElementsAre(Function(Eq("Foo"), IsEmpty(), Optional(Literal("i32")), + StatementBlock(IsEmpty(), IsEmpty()))), + UnorderedElementsAre(MappedNode("Foo", FunctionName("Foo")))); } TEST_F(SemanticsIRFactoryTest, FunctionDuplicate) { - Analyze(R"(fn Foo() {} - fn Foo() {} - )"); + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(fn Foo() {} + fn Foo() {} + )"); + ExpectRootBlock(ElementsAre(FunctionName("Foo"), FunctionName("Foo")), + UnorderedElementsAre(MappedNode("Foo", FunctionName("Foo")))); +} + +TEST_F(SemanticsIRFactoryTest, FunctionOrder) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(fn Foo() {} + fn Bar() {} + )"); + ExpectRootBlock(ElementsAre(FunctionName("Foo"), FunctionName("Bar")), + UnorderedElementsAre(MappedNode("Bar", FunctionName("Bar")), + MappedNode("Foo", FunctionName("Foo")))); +} + +TEST_F(SemanticsIRFactoryTest, TrivialReturn) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(fn Main() { + return; + } + )"); + ExpectRootBlock( + ElementsAre( + Function(Eq("Main"), IsEmpty(), IsNone(), + StatementBlock(ElementsAre(Return(IsNone())), IsEmpty()))), + UnorderedElementsAre(MappedNode("Main", FunctionName("Main")))); +} + +TEST_F(SemanticsIRFactoryTest, ReturnLiteral) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(fn Main() { + return 1; + } + )"); + ExpectRootBlock( + ElementsAre( + Function(Eq("Main"), IsEmpty(), IsNone(), + StatementBlock(ElementsAre(Return(Optional(Literal("1")))), + IsEmpty()))), + UnorderedElementsAre(MappedNode("Main", FunctionName("Main")))); +} + +TEST_F(SemanticsIRFactoryTest, ReturnArithmetic) { + EXPECT_CALL(consumer, HandleDiagnostic(_)).Times(0); + Build(R"(fn Main() { + return 1 + 2; + } + )"); + ExpectRootBlock( + ElementsAre( + Function(Eq("Main"), IsEmpty(), IsNone(), + StatementBlock(ElementsAre(Return(Optional(InfixOperator( + Literal("1"), "+", Literal("2"))))), + IsEmpty()))), + UnorderedElementsAre(MappedNode("Main", FunctionName("Main")))); } } // namespace diff --git a/toolchain/semantics/semantics_ir_for_test.cpp b/toolchain/semantics/semantics_ir_for_test.cpp new file mode 100644 index 000000000000..288ee1b6bcbb --- /dev/null +++ b/toolchain/semantics/semantics_ir_for_test.cpp @@ -0,0 +1,11 @@ +// 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 "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +llvm::Optional SemanticsIRForTest::g_semantics; + +} // namespace Carbon::Testing diff --git a/toolchain/semantics/semantics_ir_for_test.h b/toolchain/semantics/semantics_ir_for_test.h new file mode 100644 index 000000000000..fc94015c8dd2 --- /dev/null +++ b/toolchain/semantics/semantics_ir_for_test.h @@ -0,0 +1,144 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FOR_TEST_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FOR_TEST_H_ + +#include +#include + +#include + +#include "common/check.h" +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/infix_operator.h" +#include "toolchain/semantics/semantics_ir.h" + +namespace Carbon::Testing { + +// A singleton SemanticsIR instance, used by the test helpers. +// +// This provides a singleton so that calls like PrintTo(Semantics::Declaration) +// have a SemanticsIR to refer back to; PrintTo must be static. +class SemanticsIRForTest { + public: + template + static auto GetDeclaration(Semantics::Declaration decl) + -> llvm::Optional { + if (decl.kind() != NodeT::MetaNodeKind) { + return llvm::None; + } + return semantics().declarations_.Get(decl); + } + + template + static auto GetExpression(Semantics::Expression expr) + -> llvm::Optional { + if (expr.kind() != NodeT::MetaNodeKind) { + return llvm::None; + } + return semantics().expressions_.Get(expr); + } + + template + static auto GetStatement(Semantics::Statement expr) -> llvm::Optional { + if (expr.kind() != NodeT::MetaNodeKind) { + return llvm::None; + } + return semantics().statements_.Get(expr); + } + + static auto GetNodeText(ParseTree::Node node) -> llvm::StringRef { + return semantics().parse_tree_->GetNodeText(node); + } + + template + static void PrintTo(const PrintableT& printable, std::ostream* out) { + llvm::raw_os_ostream wrapped_out(*out); + semantics().Print(wrapped_out, printable); + } + + static auto semantics() -> const SemanticsIR& { + CARBON_CHECK(g_semantics != llvm::None); + return *g_semantics; + } + + static void set_semantics(SemanticsIR semantics) { + CARBON_CHECK(g_semantics == llvm::None) + << "Call clear() before setting again."; + g_semantics = std::move(semantics); + } + + static void clear() { g_semantics = llvm::None; } + + private: + static llvm::Optional g_semantics; +}; + +} // namespace Carbon::Testing + +namespace Carbon::Semantics { + +// Meta node printers. +inline void PrintTo(const Declaration& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const Expression& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const Statement& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} + +// Other node printers. +inline void PrintTo(const DeclaredName& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const ExpressionStatement& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const Function& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const InfixOperator& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const Literal& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const PatternBinding& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const Return& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} +inline void PrintTo(const StatementBlock& node, std::ostream* out) { + Carbon::Testing::SemanticsIRForTest::PrintTo(node, out); +} + +} // namespace Carbon::Semantics + +namespace llvm { + +// Prints a StringMapEntry for gmock. +inline void PrintTo( + const llvm::StringMapEntry& entry, + std::ostream* out) { + *out << "StringMapEntry(" << entry.getKey() << ", "; + Carbon::Testing::SemanticsIRForTest::PrintTo(entry.getValue(), out); + *out << ")"; +} + +// Prints a StringMapEntry for gmock. +inline void PrintTo( + const llvm::StringMapEntry& entry, + std::ostream* out) { + *out << "StringMapEntry(" << entry.getKey() << ", "; + Carbon::Testing::SemanticsIRForTest::PrintTo(entry.getValue(), out); + *out << ")"; +} + +} // namespace llvm + +#endif // CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_FOR_TEST_H_ diff --git a/toolchain/semantics/semantics_ir_test_helpers.h b/toolchain/semantics/semantics_ir_test_helpers.h new file mode 100644 index 000000000000..2c183bf2dcb6 --- /dev/null +++ b/toolchain/semantics/semantics_ir_test_helpers.h @@ -0,0 +1,54 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_TEST_HELPERS_H_ +#define CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_TEST_HELPERS_H_ + +#include +#include + +#include "common/check.h" +#include "common/ostream.h" +#include "llvm/ADT/StringExtras.h" +#include "toolchain/semantics/nodes/declared_name_test_matchers.h" +#include "toolchain/semantics/nodes/function_test_matchers.h" +#include "toolchain/semantics/nodes/infix_operator_test_matchers.h" +#include "toolchain/semantics/nodes/literal_test_matchers.h" +#include "toolchain/semantics/nodes/pattern_binding_test_matchers.h" +#include "toolchain/semantics/nodes/return_test_matchers.h" +#include "toolchain/semantics/semantics_ir_for_test.h" + +namespace Carbon::Testing { + +// TODO: Relocate these matchers. + +inline auto MappedNode(::testing::Matcher key, + ::testing::Matcher value) + -> ::testing::Matcher> { + return ::testing::AllOf( + ::testing::Property( + "key", &llvm::StringMapEntry::getKey, key), + ::testing::Property( + "value", &llvm::StringMapEntry::getValue, + value)); +} + +// Avoids gtest confusion of how to print llvm::None. +MATCHER(IsNone, "is llvm::None") { return arg == llvm::None; } + +inline auto StatementBlock( + ::testing::Matcher> nodes_matcher, + ::testing::Matcher> + name_lookup_matcher) -> ::testing::Matcher { + return ::testing::AllOf( + ::testing::Property("nodes", &Semantics::StatementBlock::nodes, + nodes_matcher), + ::testing::Property("name_lookup", + &Semantics::StatementBlock::name_lookup, + name_lookup_matcher)); +} + +} // namespace Carbon::Testing + +#endif // CARBON_TOOLCHAIN_SEMANTICS_SEMANTICS_IR_TEST_HELPERS_H_