From 4113ec8a673fc82bcb6d2fd89f3fd43c1286a130 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Wed, 15 Jun 2022 12:40:07 -0700 Subject: [PATCH] Semantics (#1213) Working on toolchain semantics: - SemanticsIR is set up as a container for the semantic tree. - SemanticsIRFactory builds the tree, with separate transformations for each ParseNodeKind. - ParseSubtreeConsumer is a helper for transforming a ParseTree::Node's children, managing size/nodes to prevent errors. - The nodes subdirectory contains SemanticIR nodes. - MetaNode is used to represent nodes which have "sub-classes": Statements, Declarations, and Expressions. - MetaNodeBlock is used to represent nodes which exist together in a block with name lookup: Statements and Declarations (not Expressions). This is traversing children first in order to address the RPO format of ParseTree. This means that when lists are formed, they're reversed to be in code-order (`FixReverseOrdering`). This is still very much incomplete -- the main intent at present is to demonstrate structure. --- toolchain/parser/BUILD | 5 +- toolchain/parser/parse_node_kind.h | 3 + toolchain/parser/parse_tree.cpp | 5 + toolchain/parser/parse_tree.h | 2 + toolchain/semantics/BUILD | 60 ++++- toolchain/semantics/function.h | 31 --- toolchain/semantics/meta_node.h | 115 +++++++++ toolchain/semantics/meta_node_block.h | 38 +++ toolchain/semantics/nodes/declared_name.h | 26 +++ .../nodes/declared_name_test_matchers.h | 28 +++ .../semantics/nodes/expression_statement.h | 31 +++ toolchain/semantics/nodes/function.h | 60 +++++ .../semantics/nodes/function_test_matchers.h | 63 +++++ toolchain/semantics/nodes/infix_operator.h | 34 +++ .../nodes/infix_operator_test_matchers.h | 39 ++++ toolchain/semantics/nodes/literal.h | 29 +++ .../semantics/nodes/literal_test_matchers.h | 33 +++ toolchain/semantics/nodes/pattern_binding.h | 33 +++ .../nodes/pattern_binding_test_matchers.h | 30 +++ toolchain/semantics/nodes/return.h | 32 +++ .../semantics/nodes/return_test_matchers.h | 32 +++ .../semantics/parse_subtree_consumer.cpp | 66 ++++++ toolchain/semantics/parse_subtree_consumer.h | 67 ++++++ toolchain/semantics/semantics_ir.cpp | 120 +++++++++- toolchain/semantics/semantics_ir.h | 84 ++++--- toolchain/semantics/semantics_ir_factory.cpp | 219 +++++++++++++++--- toolchain/semantics/semantics_ir_factory.h | 34 ++- .../semantics/semantics_ir_factory_test.cpp | 147 ++++++++++-- toolchain/semantics/semantics_ir_for_test.cpp | 11 + toolchain/semantics/semantics_ir_for_test.h | 144 ++++++++++++ .../semantics/semantics_ir_test_helpers.h | 54 +++++ 31 files changed, 1532 insertions(+), 143 deletions(-) delete mode 100644 toolchain/semantics/function.h create mode 100644 toolchain/semantics/meta_node.h create mode 100644 toolchain/semantics/meta_node_block.h create mode 100644 toolchain/semantics/nodes/declared_name.h create mode 100644 toolchain/semantics/nodes/declared_name_test_matchers.h create mode 100644 toolchain/semantics/nodes/expression_statement.h create mode 100644 toolchain/semantics/nodes/function.h create mode 100644 toolchain/semantics/nodes/function_test_matchers.h create mode 100644 toolchain/semantics/nodes/infix_operator.h create mode 100644 toolchain/semantics/nodes/infix_operator_test_matchers.h create mode 100644 toolchain/semantics/nodes/literal.h create mode 100644 toolchain/semantics/nodes/literal_test_matchers.h create mode 100644 toolchain/semantics/nodes/pattern_binding.h create mode 100644 toolchain/semantics/nodes/pattern_binding_test_matchers.h create mode 100644 toolchain/semantics/nodes/return.h create mode 100644 toolchain/semantics/nodes/return_test_matchers.h create mode 100644 toolchain/semantics/parse_subtree_consumer.cpp create mode 100644 toolchain/semantics/parse_subtree_consumer.h create mode 100644 toolchain/semantics/semantics_ir_for_test.cpp create mode 100644 toolchain/semantics/semantics_ir_for_test.h create mode 100644 toolchain/semantics/semantics_ir_test_helpers.h 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_