Browse Source

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.
Jon Ross-Perkins 3 năm trước cách đây
mục cha
commit
4113ec8a67
31 tập tin đã thay đổi với 1533 bổ sung144 xóa
  1. 4 1
      toolchain/parser/BUILD
  2. 3 0
      toolchain/parser/parse_node_kind.h
  3. 5 0
      toolchain/parser/parse_tree.cpp
  4. 2 0
      toolchain/parser/parse_tree.h
  5. 56 4
      toolchain/semantics/BUILD
  6. 0 31
      toolchain/semantics/function.h
  7. 115 0
      toolchain/semantics/meta_node.h
  8. 38 0
      toolchain/semantics/meta_node_block.h
  9. 26 0
      toolchain/semantics/nodes/declared_name.h
  10. 28 0
      toolchain/semantics/nodes/declared_name_test_matchers.h
  11. 31 0
      toolchain/semantics/nodes/expression_statement.h
  12. 60 0
      toolchain/semantics/nodes/function.h
  13. 63 0
      toolchain/semantics/nodes/function_test_matchers.h
  14. 34 0
      toolchain/semantics/nodes/infix_operator.h
  15. 39 0
      toolchain/semantics/nodes/infix_operator_test_matchers.h
  16. 29 0
      toolchain/semantics/nodes/literal.h
  17. 33 0
      toolchain/semantics/nodes/literal_test_matchers.h
  18. 33 0
      toolchain/semantics/nodes/pattern_binding.h
  19. 30 0
      toolchain/semantics/nodes/pattern_binding_test_matchers.h
  20. 32 0
      toolchain/semantics/nodes/return.h
  21. 32 0
      toolchain/semantics/nodes/return_test_matchers.h
  22. 66 0
      toolchain/semantics/parse_subtree_consumer.cpp
  23. 67 0
      toolchain/semantics/parse_subtree_consumer.h
  24. 109 11
      toolchain/semantics/semantics_ir.cpp
  25. 42 48
      toolchain/semantics/semantics_ir.h
  26. 188 25
      toolchain/semantics/semantics_ir_factory.cpp
  27. 25 11
      toolchain/semantics/semantics_ir_factory.h
  28. 134 13
      toolchain/semantics/semantics_ir_factory_test.cpp
  29. 11 0
      toolchain/semantics/semantics_ir_for_test.cpp
  30. 144 0
      toolchain/semantics/semantics_ir_for_test.h
  31. 54 0
      toolchain/semantics/semantics_ir_test_helpers.h

+ 4 - 1
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(

+ 3 - 0
toolchain/parser/parse_node_kind.h

@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <iterator>
 
+#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) {}
 

+ 5 - 0
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);

+ 2 - 0
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

+ 56 - 4
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",

+ 0 - 31
toolchain/semantics/function.h

@@ -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_

+ 115 - 0
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 <cstdint>
+#include <tuple>
+
+#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 <typename KindT, typename MetaNodeStoreT>
+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 <typename KindT, typename... StoredNodeT>
+class MetaNodeStore {
+ public:
+  using MetaNodeT = MetaNode<KindT, MetaNodeStore<KindT, StoredNodeT...>>;
+
+  // Stores the provided node, returning a pointer to it.
+  template <typename NodeT>
+  auto Store(NodeT node) -> MetaNodeT {
+    auto& node_store =
+        std::get<static_cast<size_t>(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 <typename NodeT>
+  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<int>(meta_node.kind_) << " vs "
+        << static_cast<int>(NodeT::MetaNodeKind);
+    auto& node_store =
+        std::get<static_cast<size_t>(NodeT::MetaNodeKind)>(node_stores_);
+    CARBON_CHECK(static_cast<size_t>(meta_node.index_) < node_store.size());
+    return node_store[meta_node.index_];
+  }
+
+ private:
+  std::tuple<llvm::SmallVector<StoredNodeT, 0>...> node_stores_;
+};
+
+// Meta node information for declarations.
+enum class DeclarationKind {
+  Function,
+  Invalid,
+};
+class Function;
+using DeclarationStore = MetaNodeStore<DeclarationKind, Function>;
+using Declaration = MetaNode<DeclarationKind, DeclarationStore>;
+
+// Meta node information for statements.
+enum class StatementKind {
+  ExpressionStatement,
+  Return,
+  Invalid,
+};
+class ExpressionStatement;
+class Return;
+using StatementStore =
+    MetaNodeStore<StatementKind, ExpressionStatement, Return>;
+using Statement = MetaNode<StatementKind, StatementStore>;
+
+// Meta node information for declarations.
+enum class ExpressionKind {
+  InfixOperator,
+  Literal,
+  Invalid,
+};
+class InfixOperator;
+class Literal;
+using ExpressionStore = MetaNodeStore<ExpressionKind, InfixOperator, Literal>;
+using Expression = MetaNode<ExpressionKind, ExpressionStore>;
+
+}  // namespace Carbon::Semantics
+
+#endif  // CARBON_TOOLCHAIN_SEMANTICS_META_NODE_H_

+ 38 - 0
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 <typename MetaNodeT>
+struct MetaNodeBlock {
+ public:
+  MetaNodeBlock(llvm::SmallVector<MetaNodeT, 0> nodes,
+                llvm::StringMap<MetaNodeT> name_lookup)
+      : nodes_(std::move(nodes)), name_lookup_(std::move(name_lookup)) {}
+
+  auto nodes() const -> llvm::ArrayRef<MetaNodeT> { return nodes_; }
+  auto name_lookup() const -> const llvm::StringMap<MetaNodeT>& {
+    return name_lookup_;
+  }
+
+ protected:
+  llvm::SmallVector<MetaNodeT, 0> nodes_;
+  llvm::StringMap<MetaNodeT> name_lookup_;
+};
+
+using DeclarationBlock = MetaNodeBlock<Declaration>;
+using StatementBlock = MetaNodeBlock<Statement>;
+
+}  // namespace Carbon::Semantics
+
+#endif  // CARBON_TOOLCHAIN_SEMANTICS_META_NODE_BLOCK_H_

+ 26 - 0
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_

+ 28 - 0
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 <gtest/gtest.h>
+
+#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<llvm::StringRef>(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_

+ 31 - 0
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_

+ 60 - 0
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<PatternBinding, 0> params,
+           llvm::Optional<Semantics::Expression> 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<PatternBinding> { return params_; }
+  auto return_expr() const -> llvm::Optional<Semantics::Expression> {
+    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<PatternBinding, 0> params_;
+
+  // The return expression.
+  llvm::Optional<Semantics::Expression> return_expr_;
+
+  StatementBlock body_;
+};
+
+}  // namespace Carbon::Semantics
+
+#endif  // CARBON_TOOLCHAIN_SEMANTICS_NODES_FUNCTION_H_

+ 63 - 0
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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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<llvm::StringRef>(
+                                        name_matcher))) {
+  const Semantics::Declaration& decl = arg;
+  if (auto function =
+          SemanticsIRForTest::GetDeclaration<Semantics::Function>(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<llvm::StringRef>(name_matcher),
+        ::testing::DescribeMatcher<llvm::ArrayRef<Semantics::PatternBinding>>(
+            param_matcher),
+        ::testing::DescribeMatcher<llvm::Optional<Semantics::Expression>>(
+            return_matcher),
+        ::testing::DescribeMatcher<Semantics::StatementBlock>(body_matcher))) {
+  const Semantics::Declaration& decl = arg;
+  if (auto function =
+          SemanticsIRForTest::GetDeclaration<Semantics::Function>(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_

+ 34 - 0
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_

+ 39 - 0
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 <gtest/gtest.h>
+
+#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<Semantics::Expression>(lhs_matcher),
+        ::testing::DescribeMatcher<llvm::StringRef>(op_matcher),
+        ::testing::DescribeMatcher<Semantics::Expression>(rhs_matcher))) {
+  const Semantics::Expression& expr = arg;
+  if (auto infix =
+          SemanticsIRForTest::GetExpression<Semantics::InfixOperator>(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_

+ 29 - 0
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_

+ 33 - 0
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 <gtest/gtest.h>
+
+#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<llvm::StringRef>(text_matcher))) {
+  const Semantics::Expression& expr = arg;
+  if (auto lit = SemanticsIRForTest::GetExpression<Semantics::Literal>(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_

+ 33 - 0
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_

+ 30 - 0
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 <gtest/gtest.h>
+
+#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<llvm::StringRef> name_matcher,
+    ::testing::Matcher<Semantics::Expression> type_matcher)
+    -> ::testing::Matcher<Semantics::PatternBinding> {
+  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_

+ 32 - 0
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<Expression> expr)
+      : node_(node), expr_(expr) {}
+
+  auto node() const -> ParseTree::Node { return node_; }
+  auto expression() const -> const llvm::Optional<Expression>& { return expr_; }
+
+ private:
+  ParseTree::Node node_;
+  llvm::Optional<Expression> expr_;
+};
+
+}  // namespace Carbon::Semantics
+
+#endif  // CARBON_TOOLCHAIN_SEMANTICS_NODES_RETURN_H_

+ 32 - 0
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 <gtest/gtest.h>
+
+#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<llvm::Optional<Semantics::Expression>>(
+                  expr_matcher))) {
+  const Semantics::Statement& stmt = arg;
+  if (auto ret = SemanticsIRForTest::GetStatement<Semantics::Return>(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_

+ 66 - 0
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<ParseTree::Node> {
+  if (is_done()) {
+    return llvm::None;
+  }
+  return GetNodeAndAdvance();
+}
+
+auto ParseSubtreeConsumer::TryConsume(ParseNodeKind node_kind)
+    -> llvm::Optional<ParseTree::Node> {
+  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

+ 67 - 0
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<ParseTree::PostorderIterator>;
+
+  // 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<ParseTree::Node>;
+
+  // Returns the next node if it's of the given kind.
+  [[nodiscard]] auto TryConsume(ParseNodeKind node_kind)
+      -> llvm::Optional<ParseTree::Node>;
+
+  // 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_

+ 109 - 11
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<Semantics::Function>(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<Semantics::InfixOperator>(expr));
+      return;
+    case Semantics::ExpressionKind::Literal:
+      Print(out, expressions_.Get<Semantics::Literal>(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<Semantics::ExpressionStatement>(stmt));
+      return;
+    case Semantics::StatementKind::Return:
+      Print(out, statements_.Get<Semantics::Return>(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

+ 42 - 48
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) {}
-
-   private:
-    friend class SemanticsIR;
-
-    // The kind of token. These correspond to the lists on SemanticsIR which
-    // will be indexed into.
-    enum class Kind {
-      Invalid,
-      Function,
-    };
-
-    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<Node> ordering_;
-    llvm::StringMap<Node> name_lookup_;
-  };
+  // File-level declarations.
+  auto root_block() const -> const Semantics::DeclarationBlock& {
+    return *root_block_;
+  }
+
+  // Debug printer for the parse tree.
+  void Print(llvm::raw_ostream& out, ParseTree::Node node) const;
+
+  // 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;
+
+  // 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&;
-
-  // Indexed by Token::Function.
-  llvm::SmallVector<Semantics::Function, 0> functions_;
+  Semantics::DeclarationStore declarations_;
+  Semantics::ExpressionStore expressions_;
+  Semantics::StatementStore statements_;
 
-  // The file-level block.
-  Block root_block_;
+  // The file-level block. Only assigned after initialization is complete.
+  llvm::Optional<Semantics::DeclarationBlock> root_block_;
 
   const ParseTree* parse_tree_;
 };

+ 188 - 25
toolchain/semantics/semantics_ir_factory.cpp

@@ -4,55 +4,218 @@
 
 #include "toolchain/semantics/semantics_ir_factory.h"
 
+#include <stack>
+
 #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 <typename T>
+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);
-        break;
-      case ParseNodeKind::FileEnd():
-        // No action needed.
+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<Semantics::Declaration, 0> nodes;
+  llvm::StringMap<Semantics::Declaration> name_lookup;
+  while (llvm::Optional<ParseTree::Node> 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;
+      }
       default:
-        CARBON_FATAL() << "Unhandled node kind: "
-                       << semantics_.parse_tree_->node_kind(node).name();
+        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;
 }
 
-void SemanticsIRFactory::ProcessFunctionNode(SemanticsIR::Block& block,
-                                             ParseTree::Node decl_node) {
-  llvm::Optional<Semantics::Function> 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);
+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<Semantics::Statement, 0> nodes;
+  while (llvm::Optional<ParseTree::Node> child = subtree.TryConsume()) {
+    switch (auto child_kind = parse_tree().node_kind(*child)) {
+      case ParseNodeKind::ExpressionStatement():
+        nodes.push_back(TransformExpressionStatement(*child));
         break;
-      case ParseNodeKind::ParameterList():
-        // TODO: Maybe something like Semantics::AddVariable passed to
-        // Function::AddParameter.
+      case ParseNodeKind::ReturnStatement():
+        nodes.push_back(TransformReturnStatement(*child));
         break;
-      case ParseNodeKind::CodeBlock():
-        // TODO: Should accumulate the definition into the code block.
+      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;
+  }
+}
+
+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<llvm::StringRef, Semantics::Declaration> {
+  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<Semantics::Expression> 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<Semantics::PatternBinding, 0> {
+  CARBON_CHECK(parse_tree().node_kind(node) == ParseNodeKind::ParameterList());
+
+  auto subtree = ParseSubtreeConsumer::ForParent(parse_tree(), node);
+  RequireNodeEmpty(subtree.RequireConsume(ParseNodeKind::ParameterListEnd()));
+
+  llvm::SmallVector<Semantics::PatternBinding, 0> 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

+ 25 - 11
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 <optional>
-
-#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();
-
-  // 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);
+  void Build();
+
+  // 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<llvm::StringRef, Semantics::Declaration>;
+  auto TransformInfixOperator(ParseTree::Node node) -> Semantics::InfixOperator;
+  auto TransformParameterList(ParseTree::Node node)
+      -> llvm::SmallVector<Semantics::PatternBinding, 0>;
+  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_;
 };

+ 134 - 13
toolchain/semantics/semantics_ir_factory_test.cpp

@@ -7,49 +7,170 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include <optional>
-
 #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));
+  }
+
+  ~SemanticsIRFactoryTest() override { SemanticsIRForTest::clear(); }
+
+  void ExpectRootBlock(
+      ::testing::Matcher<llvm::ArrayRef<Semantics::Declaration>> decls,
+      ::testing::Matcher<llvm::StringMap<Semantics::Declaration>> name_lookup) {
+    EXPECT_THAT(SemanticsIRForTest::semantics().root_block().nodes(), decls);
+    EXPECT_THAT(SemanticsIRForTest::semantics().root_block().name_lookup(),
+                name_lookup);
   }
 
-  std::optional<SourceBuffer> source_buffer;
-  std::optional<TokenizedBuffer> tokenized_buffer;
-  std::optional<ParseTree> parse_tree;
+  llvm::Optional<SourceBuffer> source_buffer;
+  llvm::Optional<TokenizedBuffer> tokenized_buffer;
+  llvm::Optional<ParseTree> 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

+ 11 - 0
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<SemanticsIR> SemanticsIRForTest::g_semantics;
+
+}  // namespace Carbon::Testing

+ 144 - 0
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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+#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 <typename NodeT>
+  static auto GetDeclaration(Semantics::Declaration decl)
+      -> llvm::Optional<NodeT> {
+    if (decl.kind() != NodeT::MetaNodeKind) {
+      return llvm::None;
+    }
+    return semantics().declarations_.Get<NodeT>(decl);
+  }
+
+  template <typename NodeT>
+  static auto GetExpression(Semantics::Expression expr)
+      -> llvm::Optional<NodeT> {
+    if (expr.kind() != NodeT::MetaNodeKind) {
+      return llvm::None;
+    }
+    return semantics().expressions_.Get<NodeT>(expr);
+  }
+
+  template <typename NodeT>
+  static auto GetStatement(Semantics::Statement expr) -> llvm::Optional<NodeT> {
+    if (expr.kind() != NodeT::MetaNodeKind) {
+      return llvm::None;
+    }
+    return semantics().statements_.Get<NodeT>(expr);
+  }
+
+  static auto GetNodeText(ParseTree::Node node) -> llvm::StringRef {
+    return semantics().parse_tree_->GetNodeText(node);
+  }
+
+  template <typename PrintableT>
+  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<SemanticsIR> 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<Carbon::Semantics::Declaration>& 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<Carbon::Semantics::Statement>& 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_

+ 54 - 0
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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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<std::string> key,
+                       ::testing::Matcher<Semantics::Declaration> value)
+    -> ::testing::Matcher<llvm::StringMapEntry<Semantics::Declaration>> {
+  return ::testing::AllOf(
+      ::testing::Property(
+          "key", &llvm::StringMapEntry<Semantics::Declaration>::getKey, key),
+      ::testing::Property(
+          "value", &llvm::StringMapEntry<Semantics::Declaration>::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<llvm::ArrayRef<Semantics::Statement>> nodes_matcher,
+    ::testing::Matcher<llvm::StringMap<Semantics::Statement>>
+        name_lookup_matcher) -> ::testing::Matcher<Semantics::StatementBlock> {
+  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_