Просмотр исходного кода

Expand use of IndexBase (#2436)

Initially I'd added this to lexer, this includes parser and semantics. Also adds ComparableIndexBase to unify a few common cases where <> comparisons are supported.

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Jon Ross-Perkins 3 лет назад
Родитель
Сommit
d50fef1736

+ 34 - 2
toolchain/common/index_base.h

@@ -22,14 +22,24 @@ class DataIterator;
 // DataIndex is designed to be passed by value, not reference or pointer. They
 // are also designed to be small and efficient to store in data structures.
 struct IndexBase {
-  IndexBase() : index(-1) {}
-  explicit IndexBase(int index) : index(index) {}
+  static constexpr int32_t InvalidIndex = -1;
+
+  constexpr IndexBase() : index(InvalidIndex) {}
+  constexpr explicit IndexBase(int index) : index(index) {}
 
   auto Print(llvm::raw_ostream& output) const -> void { output << index; }
 
+  auto is_valid() -> bool { return index != InvalidIndex; }
+
   int32_t index;
 };
 
+// Like IndexBase, but also provides < and > comparison operators.
+struct ComparableIndexBase : public IndexBase {
+  using IndexBase::IndexBase;
+};
+
+// Equality comparison for both IndexBase and ComparableIndexBase.
 template <typename IndexType,
           typename std::enable_if_t<std::is_base_of_v<IndexBase, IndexType>>* =
               nullptr>
@@ -43,6 +53,28 @@ auto operator!=(IndexType lhs, IndexType rhs) -> bool {
   return lhs.index != rhs.index;
 }
 
+// The < and > comparisons for only ComparableIndexBase.
+template <typename IndexType, typename std::enable_if_t<std::is_base_of_v<
+                                  ComparableIndexBase, IndexType>>* = nullptr>
+auto operator<(IndexType lhs, IndexType rhs) -> bool {
+  return lhs.index < rhs.index;
+}
+template <typename IndexType, typename std::enable_if_t<std::is_base_of_v<
+                                  ComparableIndexBase, IndexType>>* = nullptr>
+auto operator<=(IndexType lhs, IndexType rhs) -> bool {
+  return lhs.index <= rhs.index;
+}
+template <typename IndexType, typename std::enable_if_t<std::is_base_of_v<
+                                  ComparableIndexBase, IndexType>>* = nullptr>
+auto operator>(IndexType lhs, IndexType rhs) -> bool {
+  return lhs.index > rhs.index;
+}
+template <typename IndexType, typename std::enable_if_t<std::is_base_of_v<
+                                  ComparableIndexBase, IndexType>>* = nullptr>
+auto operator>=(IndexType lhs, IndexType rhs) -> bool {
+  return lhs.index >= rhs.index;
+}
+
 }  // namespace Carbon
 
 #endif  // CARBON_TOOLCHAIN_COMMON_INDEX_BASE_H_

+ 19 - 60
toolchain/lexer/tokenized_buffer.h

@@ -26,46 +26,6 @@ namespace Carbon {
 
 class TokenizedBuffer;
 
-namespace Internal {
-
-// A lightweight handle to a lexed token in a `TokenizedBuffer`.
-//
-// This type's preferred name is `TokenizedBuffer::Token` and is only defined
-// outside the class to break a dependency cycle.
-//
-// `Token` objects are designed to be passed by value, not reference or
-// pointer. They are also designed to be small and efficient to store in data
-// structures.
-//
-// `Token` objects from the same `TokenizedBuffer` can be compared with each
-// other, both for being the same token within the buffer, and to establish
-// relative position within the token stream that has been lexed out of the
-// buffer. `Token` objects from different `TokenizedBuffer`s cannot be
-// meaningfully compared.
-//
-// All other APIs to query a `Token` are on the `TokenizedBuffer`.
-class TokenizedBufferToken : public IndexBase {
- public:
-  using Token = TokenizedBufferToken;
-
-  using IndexBase::IndexBase;
-
-  friend auto operator<(Token lhs, Token rhs) -> bool {
-    return lhs.index < rhs.index;
-  }
-  friend auto operator<=(Token lhs, Token rhs) -> bool {
-    return lhs.index <= rhs.index;
-  }
-  friend auto operator>(Token lhs, Token rhs) -> bool {
-    return lhs.index > rhs.index;
-  }
-  friend auto operator>=(Token lhs, Token rhs) -> bool {
-    return lhs.index >= rhs.index;
-  }
-};
-
-}  // namespace Internal
-
 // A buffer of tokenized Carbon source code.
 //
 // This is constructed by lexing the source code text into a series of tokens.
@@ -77,7 +37,21 @@ class TokenizedBufferToken : public IndexBase {
 class TokenizedBuffer {
  public:
   // A lightweight handle to a lexed token in a `TokenizedBuffer`.
-  using Token = Internal::TokenizedBufferToken;
+  //
+  // `Token` objects are designed to be passed by value, not reference or
+  // pointer. They are also designed to be small and efficient to store in data
+  // structures.
+  //
+  // `Token` objects from the same `TokenizedBuffer` can be compared with each
+  // other, both for being the same token within the buffer, and to establish
+  // relative position within the token stream that has been lexed out of the
+  // buffer. `Token` objects from different `TokenizedBuffer`s cannot be
+  // meaningfully compared.
+  //
+  // All other APIs to query a `Token` are on the `TokenizedBuffer`.
+  struct Token : public ComparableIndexBase {
+    using ComparableIndexBase::ComparableIndexBase;
+  };
 
   // A lightweight handle to a lexed line in a `TokenizedBuffer`.
   //
@@ -90,22 +64,8 @@ class TokenizedBuffer {
   // same line or the relative position of different lines within the source.
   //
   // All other APIs to query a `Line` are on the `TokenizedBuffer`.
-  class Line : public IndexBase {
-   public:
-    using IndexBase::IndexBase;
-
-    friend auto operator<(Line lhs, Line rhs) -> bool {
-      return lhs.index < rhs.index;
-    }
-    friend auto operator<=(Line lhs, Line rhs) -> bool {
-      return lhs.index <= rhs.index;
-    }
-    friend auto operator>(Line lhs, Line rhs) -> bool {
-      return lhs.index > rhs.index;
-    }
-    friend auto operator>=(Line lhs, Line rhs) -> bool {
-      return lhs.index >= rhs.index;
-    }
+  struct Line : public ComparableIndexBase {
+    using ComparableIndexBase::ComparableIndexBase;
   };
 
   // A lightweight handle to a lexed identifier in a `TokenizedBuffer`.
@@ -119,7 +79,7 @@ class TokenizedBuffer {
   // identifier spelling. Where the identifier was written is not preserved.
   //
   // All other APIs to query a `Identifier` are on the `TokenizedBuffer`.
-  class Identifier : public IndexBase {
+  struct Identifier : public IndexBase {
     using IndexBase::IndexBase;
   };
 
@@ -206,8 +166,7 @@ class TokenizedBuffer {
 
   // A diagnostic location translator that maps token locations into source
   // buffer locations.
-  class TokenLocationTranslator
-      : public DiagnosticLocationTranslator<Internal::TokenizedBufferToken> {
+  class TokenLocationTranslator : public DiagnosticLocationTranslator<Token> {
    public:
     explicit TokenLocationTranslator(const TokenizedBuffer* buffer,
                                      int* last_line_lexed_to_column)

+ 32 - 36
toolchain/parser/parse_tree.cpp

@@ -40,8 +40,8 @@ auto ParseTree::postorder(Node n) const
   CARBON_CHECK(n.is_valid());
   // The postorder ends after this node, the root, and begins at the start of
   // its subtree.
-  int end_index = n.index_ + 1;
-  int start_index = end_index - node_impls_[n.index_].subtree_size;
+  int end_index = n.index + 1;
+  int start_index = end_index - node_impls_[n.index].subtree_size;
   return {PostorderIterator(Node(start_index)),
           PostorderIterator(Node(end_index))};
 }
@@ -49,8 +49,8 @@ auto ParseTree::postorder(Node n) const
 auto ParseTree::children(Node n) const
     -> llvm::iterator_range<SiblingIterator> {
   CARBON_CHECK(n.is_valid());
-  int end_index = n.index_ - node_impls_[n.index_].subtree_size;
-  return {SiblingIterator(*this, Node(n.index_ - 1)),
+  int end_index = n.index - node_impls_[n.index].subtree_size;
+  return {SiblingIterator(*this, Node(n.index - 1)),
           SiblingIterator(*this, Node(end_index))};
 }
 
@@ -62,38 +62,38 @@ auto ParseTree::roots() const -> llvm::iterator_range<SiblingIterator> {
 
 auto ParseTree::node_has_error(Node n) const -> bool {
   CARBON_CHECK(n.is_valid());
-  return node_impls_[n.index_].has_error;
+  return node_impls_[n.index].has_error;
 }
 
 auto ParseTree::node_kind(Node n) const -> ParseNodeKind {
   CARBON_CHECK(n.is_valid());
-  return node_impls_[n.index_].kind;
+  return node_impls_[n.index].kind;
 }
 
 auto ParseTree::node_token(Node n) const -> TokenizedBuffer::Token {
   CARBON_CHECK(n.is_valid());
-  return node_impls_[n.index_].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;
+  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);
+  return tokens_->GetTokenText(node_impls_[n.index].token);
 }
 
 auto ParseTree::PrintNode(llvm::raw_ostream& output, Node n, int depth,
                           bool preorder) const -> bool {
-  const auto& n_impl = node_impls_[n.index()];
+  const auto& n_impl = node_impls_[n.index];
   output.indent(2 * depth);
   output << "{";
   // If children are being added, include node_index in order to disambiguate
   // nodes.
   if (preorder) {
-    output << "node_index: " << n.index_ << ", ";
+    output << "node_index: " << n << ", ";
   }
   output << "kind: '" << n_impl.kind.name() << "', text: '"
          << tokens_->GetTokenText(n_impl.token) << "'";
@@ -128,14 +128,14 @@ auto ParseTree::Print(llvm::raw_ostream& output) const -> void {
     int depth;
     std::tie(n, depth) = node_stack.pop_back_val();
     for (Node sibling_n : children(n)) {
-      indents[sibling_n.index()] = depth + 1;
+      indents[sibling_n.index] = depth + 1;
       node_stack.push_back({sibling_n, depth + 1});
     }
   }
 
   output << "[\n";
   for (Node n : postorder()) {
-    PrintNode(output, n, indents[n.index()], /*adding_children=*/false);
+    PrintNode(output, n, indents[n.index], /*adding_children=*/false);
     output << ",\n";
   }
   output << "]\n";
@@ -192,12 +192,12 @@ auto ParseTree::Verify() const -> std::optional<Error> {
   llvm::SmallVector<ParseTree::Node> nodes;
   // Traverse the tree in postorder.
   for (Node n : postorder()) {
-    const auto& n_impl = node_impls_[n.index()];
+    const auto& n_impl = node_impls_[n.index];
 
     if (n_impl.has_error && !has_errors_) {
       return Error(llvm::formatv(
           "Node #{0} has errors, but the tree is not marked as having any.",
-          n.index()));
+          n.index));
     }
 
     int subtree_size = 1;
@@ -207,9 +207,9 @@ auto ParseTree::Verify() const -> std::optional<Error> {
           return Error(
               llvm::formatv("Node #{0} is a {1} with bracket {2}, but didn't "
                             "find the bracket.",
-                            n.index(), n_impl.kind, n_impl.kind.bracket()));
+                            n, n_impl.kind, n_impl.kind.bracket()));
         }
-        auto child_impl = node_impls_[nodes.pop_back_val().index()];
+        auto child_impl = node_impls_[nodes.pop_back_val().index];
         subtree_size += child_impl.subtree_size;
         if (n_impl.kind.bracket() == child_impl.kind) {
           break;
@@ -221,35 +221,35 @@ auto ParseTree::Verify() const -> std::optional<Error> {
           return Error(llvm::formatv(
               "Node #{0} is a {1} with child_count {2}, but only had {3} "
               "nodes to consume.",
-              n.index(), n_impl.kind, n_impl.kind.child_count(), i));
+              n, n_impl.kind, n_impl.kind.child_count(), i));
         }
-        auto child_impl = node_impls_[nodes.pop_back_val().index()];
+        auto child_impl = node_impls_[nodes.pop_back_val().index];
         subtree_size += child_impl.subtree_size;
       }
     }
     if (n_impl.subtree_size != subtree_size) {
       return Error(llvm::formatv(
-          "Node #{0} is a {1} with subtree_size of {2}, but calculated {3}.",
-          n.index(), n_impl.kind, n_impl.subtree_size, subtree_size));
+          "Node #{0} is a {1} with subtree_size of {2}, but calculated {3}.", n,
+          n_impl.kind, n_impl.subtree_size, subtree_size));
     }
     nodes.push_back(n);
   }
 
   // Remaining nodes should all be roots in the tree; make sure they line up.
-  CARBON_CHECK(nodes.back().index() ==
+  CARBON_CHECK(nodes.back().index ==
                static_cast<int32_t>(node_impls_.size()) - 1)
-      << nodes.back().index() << " " << node_impls_.size() - 1;
+      << nodes.back() << " " << node_impls_.size() - 1;
   int prev_index = -1;
   for (const auto& n : nodes) {
-    const auto& n_impl = node_impls_[n.index()];
+    const auto& n_impl = node_impls_[n.index];
 
-    if (n.index() - n_impl.subtree_size != prev_index) {
-      return Error(llvm::formatv(
-          "Node #{0} is a root {1} with subtree_size {2}, but "
-          "previous root was at #{3}.",
-          n.index(), n_impl.kind, n_impl.subtree_size, prev_index));
+    if (n.index - n_impl.subtree_size != prev_index) {
+      return Error(
+          llvm::formatv("Node #{0} is a root {1} with subtree_size {2}, but "
+                        "previous root was at #{3}.",
+                        n, n_impl.kind, n_impl.subtree_size, prev_index));
     }
-    prev_index = n.index();
+    prev_index = n.index;
   }
 
   if (!has_errors_ &&
@@ -262,18 +262,14 @@ auto ParseTree::Verify() const -> std::optional<Error> {
   return std::nullopt;
 }
 
-auto ParseTree::Node::Print(llvm::raw_ostream& output) const -> void {
-  output << index();
-}
-
 auto ParseTree::PostorderIterator::Print(llvm::raw_ostream& output) const
     -> void {
-  output << node_.index();
+  output << node_;
 }
 
 auto ParseTree::SiblingIterator::Print(llvm::raw_ostream& output) const
     -> void {
-  output << node_.index();
+  output << node_;
 }
 
 }  // namespace Carbon

+ 7 - 59
toolchain/parser/parse_tree.h

@@ -42,7 +42,7 @@ namespace Carbon {
 // applied.
 class ParseTree {
  public:
-  class Node;
+  struct Node;
   class PostorderIterator;
   class SiblingIterator;
 
@@ -241,60 +241,8 @@ class ParseTree {
 //
 // That said, nodes can be compared and are part of a depth-first pre-order
 // sequence across all nodes in the parse tree.
-class ParseTree::Node {
- public:
-  // Node handles are default constructable, but such a node cannot be used
-  // for anything. It just allows it to be initialized later through
-  // assignment. Any other operation on a default constructed node is an
-  // error.
-  Node() = default;
-
-  friend auto operator==(Node lhs, Node rhs) -> bool {
-    return lhs.index_ == rhs.index_;
-  }
-  friend auto operator!=(Node lhs, Node rhs) -> bool {
-    return lhs.index_ != rhs.index_;
-  }
-  friend auto operator<(Node lhs, Node rhs) -> bool {
-    return lhs.index_ < rhs.index_;
-  }
-  friend auto operator<=(Node lhs, Node rhs) -> bool {
-    return lhs.index_ <= rhs.index_;
-  }
-  friend auto operator>(Node lhs, Node rhs) -> bool {
-    return lhs.index_ > rhs.index_;
-  }
-  friend auto operator>=(Node lhs, Node rhs) -> bool {
-    return lhs.index_ >= rhs.index_;
-  }
-
-  // Returns an opaque integer identifier of the node in the tree. Clients
-  // should not expect any particular semantics from this value.
-  //
-  // TODO: Maybe we can switch to stream operator overloads?
-  [[nodiscard]] auto index() const -> int { return index_; }
-
-  // Prints the node index.
-  auto Print(llvm::raw_ostream& output) const -> void;
-
-  // Returns true if the node is valid; in other words, it was not default
-  // initialized.
-  auto is_valid() -> bool { return index_ != InvalidValue; }
-
- private:
-  friend ParseTree;
-  friend PostorderIterator;
-  friend SiblingIterator;
-
-  // Value for uninitialized nodes.
-  static constexpr int InvalidValue = -1;
-
-  // Constructs a node with a specific index into the parse tree's postorder
-  // sequence of node implementations.
-  explicit Node(int index) : index_(index) {}
-
-  // The index of this node's implementation in the postorder sequence.
-  int32_t index_ = InvalidValue;
+struct ParseTree::Node : public ComparableIndexBase {
+  using ComparableIndexBase::ComparableIndexBase;
 };
 
 // A random-access iterator to the depth-first postorder sequence of parse nodes
@@ -320,15 +268,15 @@ class ParseTree::PostorderIterator
   auto operator*() const -> Node { return node_; }
 
   auto operator-(const PostorderIterator& rhs) const -> int {
-    return node_.index_ - rhs.node_.index_;
+    return node_.index - rhs.node_.index;
   }
 
   auto operator+=(int offset) -> PostorderIterator& {
-    node_.index_ += offset;
+    node_.index += offset;
     return *this;
   }
   auto operator-=(int offset) -> PostorderIterator& {
-    node_.index_ -= offset;
+    node_.index -= offset;
     return *this;
   }
 
@@ -373,7 +321,7 @@ class ParseTree::SiblingIterator
 
   using iterator_facade_base::operator++;
   auto operator++() -> SiblingIterator& {
-    node_.index_ -= std::abs(tree_->node_impls_[node_.index_].subtree_size);
+    node_.index -= std::abs(tree_->node_impls_[node_.index].subtree_size);
     return *this;
   }
 

+ 2 - 2
toolchain/semantics/semantics_ir.cpp

@@ -26,7 +26,7 @@ auto SemanticsIR::MakeBuiltinIR() -> SemanticsIR {
                                            SemanticsNodeId(TypeOfTypeType)));
   semantics.cross_references_[SemanticsBuiltinKind::TypeType().AsInt()] =
       SemanticsCrossReference(BuiltinIR, block_id, type_type);
-  CARBON_CHECK(type_type.id == TypeOfTypeType)
+  CARBON_CHECK(type_type.index == TypeOfTypeType)
       << "TypeType's type must be self-referential.";
 
   constexpr int32_t TypeOfInvalidType = 1;
@@ -35,7 +35,7 @@ auto SemanticsIR::MakeBuiltinIR() -> SemanticsIR {
                                            SemanticsNodeId(TypeOfInvalidType)));
   semantics.cross_references_[SemanticsBuiltinKind::InvalidType().AsInt()] =
       SemanticsCrossReference(BuiltinIR, block_id, invalid_type);
-  CARBON_CHECK(invalid_type.id == TypeOfInvalidType)
+  CARBON_CHECK(invalid_type.index == TypeOfInvalidType)
       << "InvalidType's type must be self-referential.";
 
   auto integer_literal_type = semantics.AddNode(

+ 7 - 20
toolchain/semantics/semantics_ir.h

@@ -18,22 +18,9 @@ class SemanticsIRForTest;
 namespace Carbon {
 
 // The ID of a cross-referenced IR (within cross_reference_irs_).
-struct SemanticsCrossReferenceIRId {
-  SemanticsCrossReferenceIRId() : id(-1) {}
-  constexpr explicit SemanticsCrossReferenceIRId(int32_t id) : id(id) {}
-
-  friend auto operator==(SemanticsCrossReferenceIRId lhs,
-                         SemanticsCrossReferenceIRId rhs) -> bool {
-    return lhs.id == rhs.id;
-  }
-  friend auto operator!=(SemanticsCrossReferenceIRId lhs,
-                         SemanticsCrossReferenceIRId rhs) -> bool {
-    return lhs.id != rhs.id;
-  }
-
-  auto Print(llvm::raw_ostream& out) const -> void { out << "ir" << id; }
-
-  int32_t id;
+struct SemanticsCrossReferenceIRId : public IndexBase {
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void { out << "ir" << index; }
 };
 
 // A cross-reference between node blocks or IRs; essentially, anything that's
@@ -87,8 +74,8 @@ class SemanticsIR {
       -> SemanticsNodeId {
     if (node_id.is_cross_reference()) {
       auto ref = cross_references_[node_id.GetAsCrossReference()];
-      auto type = cross_reference_irs_[ref.ir.id]
-                      ->node_blocks_[ref.node_block.id][ref.node.id]
+      auto type = cross_reference_irs_[ref.ir.index]
+                      ->node_blocks_[ref.node_block.index][ref.node.index]
                       .type();
       if (type.is_cross_reference() ||
           (ref.ir == ThisIR && ref.node_block == block_id)) {
@@ -103,7 +90,7 @@ class SemanticsIR {
         CARBON_FATAL() << "Need to think more about this case";
       }
     } else {
-      return node_blocks_[block_id.id][node_id.id].type();
+      return node_blocks_[block_id.index][node_id.index].type();
     }
   }
 
@@ -135,7 +122,7 @@ class SemanticsIR {
   // Adds a node to a specified block, returning an ID to reference the node.
   auto AddNode(SemanticsNodeBlockId block_id, SemanticsNode node)
       -> SemanticsNodeId {
-    auto& block = node_blocks_[block_id.id];
+    auto& block = node_blocks_[block_id.index];
     SemanticsNodeId node_id(block.size());
     block.push_back(node);
     return node_id;

+ 1 - 1
toolchain/semantics/semantics_node.cpp

@@ -31,7 +31,7 @@ void SemanticsNode::Print(llvm::raw_ostream& out) const {
 #include "toolchain/semantics/semantics_node_kind.def"
   }
   out << ")";
-  if (type_.id != -1) {
+  if (type_.index != -1) {
     out << ": " << type_;
   }
 }

+ 22 - 73
toolchain/semantics/semantics_node.h

@@ -16,12 +16,12 @@
 namespace Carbon {
 
 // Type-safe storage of Node IDs.
-struct SemanticsNodeId {
+struct SemanticsNodeId : public IndexBase {
   static constexpr int32_t CrossReferenceBit = 0x8000'0000;
 
   // Constructs a cross-reference node ID.
-  static auto MakeCrossReference(int32_t id) -> SemanticsNodeId {
-    return SemanticsNodeId(id | CrossReferenceBit);
+  static auto MakeCrossReference(int32_t index) -> SemanticsNodeId {
+    return SemanticsNodeId(index | CrossReferenceBit);
   }
   // Constructs a cross-reference node ID for a builtin. This relies on
   // SemanticsIR guarantees for builtin cross-reference placement.
@@ -30,91 +30,40 @@ struct SemanticsNodeId {
     return MakeCrossReference(kind.AsInt());
   }
 
-  SemanticsNodeId() : id(-1) {}
-  explicit SemanticsNodeId(int32_t id) : id(id) {}
-  SemanticsNodeId(SemanticsNodeId const&) = default;
-  auto operator=(const SemanticsNodeId& other) -> SemanticsNodeId& = default;
+  using IndexBase::IndexBase;
 
-  auto is_cross_reference() const -> bool { return id & CrossReferenceBit; }
+  auto is_cross_reference() const -> bool { return index & CrossReferenceBit; }
   // Returns the ID for a cross-reference, just handling removal of the marker
   // bit.
   auto GetAsCrossReference() const -> int32_t {
-    return id & ~CrossReferenceBit;
-  }
-
-  friend auto operator==(SemanticsNodeId lhs, SemanticsNodeId rhs) -> bool {
-    return lhs.id == rhs.id;
-  }
-  friend auto operator!=(SemanticsNodeId lhs, SemanticsNodeId rhs) -> bool {
-    return lhs.id != rhs.id;
+    return index & ~CrossReferenceBit;
   }
 
   auto Print(llvm::raw_ostream& out) const -> void {
     if (is_cross_reference()) {
       out << "node_xref" << GetAsCrossReference();
     } else {
-      out << "node" << id;
+      out << "node" << index;
     }
   }
-
-  int32_t id;
 };
 
 // Type-safe storage of identifiers.
-struct SemanticsIdentifierId {
-  SemanticsIdentifierId() : id(-1) {}
-  explicit SemanticsIdentifierId(int32_t id) : id(id) {}
-
-  friend auto operator==(SemanticsIdentifierId lhs, SemanticsIdentifierId rhs)
-      -> bool {
-    return lhs.id == rhs.id;
-  }
-  friend auto operator!=(SemanticsIdentifierId lhs, SemanticsIdentifierId rhs)
-      -> bool {
-    return lhs.id != rhs.id;
-  }
-
-  auto Print(llvm::raw_ostream& out) const -> void { out << "ident" << id; }
-
-  int32_t id;
+struct SemanticsIdentifierId : public IndexBase {
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void { out << "ident" << index; }
 };
 
 // Type-safe storage of integer literals.
-struct SemanticsIntegerLiteralId {
-  SemanticsIntegerLiteralId() : id(-1) {}
-  explicit SemanticsIntegerLiteralId(int32_t id) : id(id) {}
-
-  friend auto operator==(SemanticsIntegerLiteralId lhs,
-                         SemanticsIntegerLiteralId rhs) -> bool {
-    return lhs.id == rhs.id;
-  }
-  friend auto operator!=(SemanticsIntegerLiteralId lhs,
-                         SemanticsIntegerLiteralId rhs) -> bool {
-    return lhs.id != rhs.id;
-  }
-
-  auto Print(llvm::raw_ostream& out) const -> void { out << "int" << id; }
-
-  int32_t id;
+struct SemanticsIntegerLiteralId : public IndexBase {
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void { out << "int" << index; }
 };
 
 // Type-safe storage of node blocks.
-struct SemanticsNodeBlockId {
-  SemanticsNodeBlockId() : id(-1) {}
-  explicit SemanticsNodeBlockId(int32_t id) : id(id) {}
-
-  friend auto operator==(SemanticsNodeBlockId lhs, SemanticsNodeBlockId rhs)
-      -> bool {
-    return lhs.id == rhs.id;
-  }
-  friend auto operator!=(SemanticsNodeBlockId lhs, SemanticsNodeBlockId rhs)
-      -> bool {
-    return lhs.id != rhs.id;
-  }
-
-  auto Print(llvm::raw_ostream& out) const -> void { out << "block" << id; }
-
-  int32_t id;
+struct SemanticsNodeBlockId : public IndexBase {
+  using IndexBase::IndexBase;
+  auto Print(llvm::raw_ostream& out) const -> void { out << "block" << index; }
 };
 
 // The standard structure for nodes.
@@ -128,7 +77,7 @@ class SemanticsNode {
                                     SemanticsNodeId type, SemanticsNodeId lhs,
                                     SemanticsNodeId rhs) -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::BinaryOperatorAdd(),
-                         type, lhs.id, rhs.id);
+                         type, lhs.index, rhs.index);
   }
   auto GetAsBinaryOperatorAdd() const
       -> std::pair<SemanticsNodeId, SemanticsNodeId> {
@@ -140,7 +89,7 @@ class SemanticsNode {
                            SemanticsIdentifierId name, SemanticsNodeId node)
       -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::BindName(),
-                         SemanticsNodeId(), name.id, node.id);
+                         SemanticsNodeId(), name.index, node.index);
   }
   auto GetAsBindName() const
       -> std::pair<SemanticsIdentifierId, SemanticsNodeId> {
@@ -163,7 +112,7 @@ class SemanticsNode {
   static auto MakeCodeBlock(ParseTree::Node parse_node,
                             SemanticsNodeBlockId node_block) -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::CodeBlock(),
-                         SemanticsNodeId(), node_block.id);
+                         SemanticsNodeId(), node_block.index);
   }
   auto GetAsCodeBlock() const -> SemanticsNodeBlockId {
     CARBON_CHECK(kind_ == SemanticsNodeKind::CodeBlock());
@@ -186,7 +135,7 @@ class SemanticsNode {
                                      SemanticsNodeBlockId node_block)
       -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::FunctionDefinition(),
-                         SemanticsNodeId(), decl.id, node_block.id);
+                         SemanticsNodeId(), decl.index, node_block.index);
   }
   auto GetAsFunctionDefinition() const
       -> std::pair<SemanticsNodeId, SemanticsNodeBlockId> {
@@ -200,7 +149,7 @@ class SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::IntegerLiteral(),
                          SemanticsNodeId::MakeBuiltinReference(
                              SemanticsBuiltinKind::IntegerLiteralType()),
-                         integer.id);
+                         integer.index);
   }
   auto GetAsIntegerLiteral() const -> SemanticsIntegerLiteralId {
     CARBON_CHECK(kind_ == SemanticsNodeKind::IntegerLiteral());
@@ -233,7 +182,7 @@ class SemanticsNode {
                                    SemanticsNodeId type, SemanticsNodeId expr)
       -> SemanticsNode {
     return SemanticsNode(parse_node, SemanticsNodeKind::ReturnExpression(),
-                         type, expr.id);
+                         type, expr.index);
   }
   auto GetAsReturnExpression() const -> SemanticsNodeId {
     CARBON_CHECK(kind_ == SemanticsNodeKind::ReturnExpression());

+ 1 - 1
toolchain/semantics/semantics_parse_tree_handler.cpp

@@ -113,7 +113,7 @@ auto SemanticsParseTreeHandler::Build() -> void {
         break;
       }
       default: {
-        CARBON_FATAL() << "In ParseTree at index " << parse_node.index()
+        CARBON_FATAL() << "In ParseTree at index " << parse_node
                        << ", unhandled NodeKind " << parse_kind;
       }
     }