Procházet zdrojové kódy

Combine DiagnosticConverter into DiagnosticEmitter (#4878)

At present, we typically define a DiagnosticConverter, then store an
instance of it and a DiagnosticEmitter that wraps it. This is relatively
minor in general, but I've been trying to create more self-contained
DiagnosticEmitter classes (which hold their own DiagnosticConverter,
similar to NullDiagnosticEmitter), and there it just gets in the way.

Since we don't reuse DiagnosticConverter instances, this combines the
definition into DiagnosticEmitter. Mainly this means we don't have a
separate object in play, and less to carry around.

The most impact is probably to SemIRDiagnosticConverter, which was also
the most complex. Now `SemIRLocDiagnosticEmitter`, this gets some
different construction flow. Note in the PR I've split the file rename
to its own commit, to try to help delta views. However, the most
substantial parts of the refactoring are split into #4876, which this
depends upon.
Jon Ross-Perkins před 1 rokem
rodič
revize
e79d3be5bd

+ 4 - 4
toolchain/check/BUILD

@@ -139,7 +139,7 @@ cc_library(
         ":dump",
         ":impl",
         ":pointer_dereference",
-        ":sem_ir_diagnostic_converter",
+        ":sem_ir_loc_diagnostic_emitter",
         "//common:check",
         "//common:error",
         "//common:map",
@@ -263,9 +263,9 @@ cc_library(
 )
 
 cc_library(
-    name = "sem_ir_diagnostic_converter",
-    srcs = ["sem_ir_diagnostic_converter.cpp"],
-    hdrs = ["sem_ir_diagnostic_converter.h"],
+    name = "sem_ir_loc_diagnostic_emitter",
+    srcs = ["sem_ir_loc_diagnostic_emitter.cpp"],
+    hdrs = ["sem_ir_loc_diagnostic_emitter.h"],
     deps = [
         ":context",
         "//common:raw_string_ostream",

+ 6 - 3
toolchain/check/check.cpp

@@ -9,7 +9,7 @@
 #include "toolchain/check/check_unit.h"
 #include "toolchain/check/context.h"
 #include "toolchain/check/diagnostic_helpers.h"
-#include "toolchain/check/sem_ir_diagnostic_converter.h"
+#include "toolchain/check/sem_ir_loc_diagnostic_emitter.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/lex/token_kind.h"
@@ -320,9 +320,12 @@ auto CheckParseTrees(llvm::MutableArrayRef<Unit> units, bool prelude_import,
   // UnitAndImports is big due to its SmallVectors, so we default to 0 on the
   // stack.
   llvm::SmallVector<UnitAndImports, 0> unit_infos;
+  llvm::SmallVector<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters;
   unit_infos.reserve(units.size());
+  tree_and_subtrees_getters.reserve(units.size());
   for (auto [i, unit] : llvm::enumerate(units)) {
     unit_infos.emplace_back(SemIR::CheckIRId(i), unit);
+    tree_and_subtrees_getters.push_back(unit.tree_and_subtrees_getter);
   }
 
   Map<ImportKey, UnitAndImports*> api_map =
@@ -372,7 +375,7 @@ auto CheckParseTrees(llvm::MutableArrayRef<Unit> units, bool prelude_import,
   for (int check_index = 0;
        check_index < static_cast<int>(ready_to_check.size()); ++check_index) {
     auto* unit_info = ready_to_check[check_index];
-    CheckUnit(unit_info, units.size(), fs, vlog_stream).Run();
+    CheckUnit(unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
     for (auto* incoming_import : unit_info->incoming_imports) {
       --incoming_import->imports_remaining;
       if (incoming_import->imports_remaining == 0) {
@@ -419,7 +422,7 @@ auto CheckParseTrees(llvm::MutableArrayRef<Unit> units, bool prelude_import,
     // incomplete imports.
     for (auto& unit_info : unit_infos) {
       if (unit_info.imports_remaining > 0) {
-        CheckUnit(&unit_info, units.size(), fs, vlog_stream).Run();
+        CheckUnit(&unit_info, tree_and_subtrees_getters, fs, vlog_stream).Run();
       }
     }
   }

+ 2 - 5
toolchain/check/check.h

@@ -8,7 +8,7 @@
 #include "common/ostream.h"
 #include "toolchain/base/shared_value_stores.h"
 #include "toolchain/base/timings.h"
-#include "toolchain/check/sem_ir_diagnostic_converter.h"
+#include "toolchain/check/sem_ir_loc_diagnostic_emitter.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/parse/tree_and_subtrees.h"
 #include "toolchain/sem_ir/file.h"
@@ -23,13 +23,10 @@ struct Unit {
   Timings* timings;
 
   // Returns a lazily constructed TreeAndSubtrees.
-  Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees;
+  Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter;
 
   // The unit's SemIR, provided as empty and filled in by CheckParseTrees.
   SemIR::File* sem_ir;
-
-  // Diagnostic converters.
-  SemIRDiagnosticConverter* sem_ir_converter;
 };
 
 // Checks a group of parse trees. This will use imports to decide the order of

+ 13 - 12
toolchain/check/check_unit.cpp

@@ -34,19 +34,21 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
   return count;
 }
 
-CheckUnit::CheckUnit(UnitAndImports* unit_and_imports, int total_ir_count,
-                     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                     llvm::raw_ostream* vlog_stream)
+CheckUnit::CheckUnit(
+    UnitAndImports* unit_and_imports,
+    llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+    llvm::raw_ostream* vlog_stream)
     : unit_and_imports_(unit_and_imports),
-      total_ir_count_(total_ir_count),
+      total_ir_count_(tree_and_subtrees_getters.size()),
       fs_(std::move(fs)),
       vlog_stream_(vlog_stream),
-      emitter_(*unit_and_imports_->unit->sem_ir_converter,
-               unit_and_imports_->err_tracker),
-      context_(&emitter_, unit_and_imports_->unit->get_parse_tree_and_subtrees,
+      emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters,
+               unit_and_imports_->unit->sem_ir),
+      context_(&emitter_, unit_and_imports_->unit->tree_and_subtrees_getter,
                unit_and_imports_->unit->sem_ir,
-               GetImportedIRCount(unit_and_imports), total_ir_count,
-               vlog_stream) {}
+               GetImportedIRCount(unit_and_imports),
+               tree_and_subtrees_getters.size(), vlog_stream) {}
 
 auto CheckUnit::Run() -> void {
   Timings::ScopedTiming timing(unit_and_imports_->unit->timings, "check");
@@ -363,7 +365,7 @@ auto CheckUnit::ProcessNodeIds() -> bool {
 
   // On crash, report which token we were handling.
   PrettyStackTraceFunction node_dumper([&](llvm::raw_ostream& output) {
-    const auto& tree = unit_and_imports_->unit->get_parse_tree_and_subtrees();
+    const auto& tree = unit_and_imports_->unit->tree_and_subtrees_getter();
     auto converted = tree.NodeToDiagnosticLoc(node_id, /*token_only=*/false);
     converted.loc.FormatLocation(output);
     output << "checking " << context_.parse_tree().node_kind(node_id) << "\n";
@@ -374,8 +376,7 @@ auto CheckUnit::ProcessNodeIds() -> bool {
   while (auto maybe_node_id = traversal.Next()) {
     node_id = *maybe_node_id;
 
-    unit_and_imports_->unit->sem_ir_converter->AdvanceToken(
-        context_.parse_tree().node_token(node_id));
+    emitter_.AdvanceToken(context_.parse_tree().node_token(node_id));
 
     if (context_.parse_tree().node_has_error(node_id)) {
       context_.TODO(node_id, "handle invalid parse trees in `check`");

+ 28 - 26
toolchain/check/check_unit.h

@@ -9,6 +9,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/check/check.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/sem_ir_loc_diagnostic_emitter.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -43,33 +44,33 @@ struct PackageImports {
   llvm::SmallVector<Import> imports;
 };
 
-// Converts a `NodeId` to a diagnostic location for `UnitAndImports`.
-class UnitAndImportsDiagnosticConverter
-    : public DiagnosticConverter<Parse::NodeId> {
- public:
-  explicit UnitAndImportsDiagnosticConverter(
-      Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees)
-      : get_parse_tree_and_subtrees_(get_parse_tree_and_subtrees) {}
-
-  auto ConvertLoc(Parse::NodeId node_id, ContextFnT /*context_fn*/) const
-      -> ConvertedDiagnosticLoc override {
-    return get_parse_tree_and_subtrees_().NodeToDiagnosticLoc(
-        node_id, /*token_only=*/false);
-  }
-
- private:
-  Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees_;
-};
-
 // Contains information accumulated while checking a `Unit` (primarily import
 // information), in addition to the `Unit` itself.
 struct UnitAndImports {
+  // Converts a `NodeId` to a diagnostic location for `UnitAndImports`.
+  class Emitter : public DiagnosticEmitter<Parse::NodeId> {
+   public:
+    explicit Emitter(DiagnosticConsumer* consumer,
+                     Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter)
+        : DiagnosticEmitter(consumer),
+          tree_and_subtrees_getter_(tree_and_subtrees_getter) {}
+
+   protected:
+    auto ConvertLoc(Parse::NodeId node_id, ContextFnT /*context_fn*/) const
+        -> ConvertedDiagnosticLoc override {
+      return tree_and_subtrees_getter_().NodeToDiagnosticLoc(
+          node_id, /*token_only=*/false);
+    }
+
+   private:
+    Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter_;
+  };
+
   explicit UnitAndImports(SemIR::CheckIRId check_ir_id, Unit& unit)
       : check_ir_id(check_ir_id),
         unit(&unit),
         err_tracker(*unit.consumer),
-        converter(unit.get_parse_tree_and_subtrees),
-        emitter(converter, err_tracker) {}
+        emitter(&err_tracker, unit.tree_and_subtrees_getter) {}
 
   auto parse_tree() -> const Parse::Tree& { return unit->sem_ir->parse_tree(); }
   auto source() -> const SourceBuffer& {
@@ -81,8 +82,7 @@ struct UnitAndImports {
 
   // Emitter information.
   ErrorTrackingDiagnosticConsumer err_tracker;
-  UnitAndImportsDiagnosticConverter converter;
-  DiagnosticEmitter<Parse::NodeId> emitter;
+  Emitter emitter;
 
   // List of the outgoing imports. If a package includes unavailable library
   // imports, it has an entry with has_load_error set. Invalid imports (for
@@ -119,9 +119,11 @@ struct UnitAndImports {
 // logic in check.cpp.
 class CheckUnit {
  public:
-  explicit CheckUnit(UnitAndImports* unit_and_imports, int total_ir_count,
-                     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
-                     llvm::raw_ostream* vlog_stream);
+  explicit CheckUnit(
+      UnitAndImports* unit_and_imports,
+      llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
+      llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
+      llvm::raw_ostream* vlog_stream);
 
   // Produces and checks the IR for the provided unit.
   auto Run() -> void;
@@ -171,7 +173,7 @@ class CheckUnit {
   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
   llvm::raw_ostream* vlog_stream_;
 
-  Context::DiagnosticEmitter emitter_;
+  SemIRLocDiagnosticEmitter emitter_;
   Context context_;
 };
 

+ 2 - 2
toolchain/check/context.cpp

@@ -39,11 +39,11 @@
 namespace Carbon::Check {
 
 Context::Context(DiagnosticEmitter* emitter,
-                 Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees,
+                 Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
                  SemIR::File* sem_ir, int imported_ir_count, int total_ir_count,
                  llvm::raw_ostream* vlog_stream)
     : emitter_(emitter),
-      get_parse_tree_and_subtrees_(get_parse_tree_and_subtrees),
+      tree_and_subtrees_getter_(tree_and_subtrees_getter),
       sem_ir_(sem_ir),
       vlog_stream_(vlog_stream),
       node_stack_(sem_ir->parse_tree(), vlog_stream),

+ 3 - 3
toolchain/check/context.h

@@ -73,7 +73,7 @@ class Context {
 
   // Stores references for work.
   explicit Context(DiagnosticEmitter* emitter,
-                   Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees,
+                   Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter,
                    SemIR::File* sem_ir, int imported_ir_count,
                    int total_ir_count, llvm::raw_ostream* vlog_stream);
 
@@ -540,7 +540,7 @@ class Context {
   auto emitter() -> DiagnosticEmitter& { return *emitter_; }
 
   auto parse_tree_and_subtrees() -> const Parse::TreeAndSubtrees& {
-    return get_parse_tree_and_subtrees_();
+    return tree_and_subtrees_getter_();
   }
 
   auto sem_ir() -> SemIR::File& { return *sem_ir_; }
@@ -747,7 +747,7 @@ class Context {
   DiagnosticEmitter* emitter_;
 
   // Returns a lazily constructed TreeAndSubtrees.
-  Parse::GetTreeAndSubtreesFn get_parse_tree_and_subtrees_;
+  Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter_;
 
   // The SemIR::File being added to.
   SemIR::File* sem_ir_;

+ 1 - 1
toolchain/check/diagnostic_helpers.h

@@ -33,7 +33,7 @@ class SemIRLoc {
 
  private:
   // Only allow member access for diagnostics.
-  friend class SemIRDiagnosticConverter;
+  friend class SemIRLocDiagnosticEmitter;
 
   union {
     SemIR::InstId inst_id_;

+ 9 - 9
toolchain/check/sem_ir_diagnostic_converter.cpp → toolchain/check/sem_ir_loc_diagnostic_emitter.cpp

@@ -2,7 +2,7 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-#include "toolchain/check/sem_ir_diagnostic_converter.h"
+#include "toolchain/check/sem_ir_loc_diagnostic_emitter.h"
 
 #include "common/raw_string_ostream.h"
 #include "toolchain/sem_ir/absolute_node_id.h"
@@ -10,8 +10,8 @@
 
 namespace Carbon::Check {
 
-auto SemIRDiagnosticConverter::ConvertLoc(SemIRLoc loc,
-                                          ContextFnT context_fn) const
+auto SemIRLocDiagnosticEmitter::ConvertLoc(SemIRLoc loc,
+                                           ContextFnT context_fn) const
     -> ConvertedDiagnosticLoc {
   auto converted = ConvertLocImpl(loc, context_fn);
 
@@ -33,8 +33,8 @@ auto SemIRDiagnosticConverter::ConvertLoc(SemIRLoc loc,
   return converted;
 }
 
-auto SemIRDiagnosticConverter::ConvertLocImpl(SemIRLoc loc,
-                                              ContextFnT context_fn) const
+auto SemIRLocDiagnosticEmitter::ConvertLocImpl(SemIRLoc loc,
+                                               ContextFnT context_fn) const
     -> ConvertedDiagnosticLoc {
   llvm::SmallVector<SemIR::AbsoluteNodeId> absolute_node_ids =
       loc.is_inst_id_ ? SemIR::GetAbsoluteNodeId(sem_ir_, loc.inst_id_)
@@ -57,16 +57,16 @@ auto SemIRDiagnosticConverter::ConvertLocImpl(SemIRLoc loc,
   return ConvertLocInFile(final_node_id, loc.token_only_, context_fn);
 }
 
-auto SemIRDiagnosticConverter::ConvertLocInFile(
+auto SemIRLocDiagnosticEmitter::ConvertLocInFile(
     SemIR::AbsoluteNodeId absolute_node_id, bool token_only,
     ContextFnT /*context_fn*/) const -> ConvertedDiagnosticLoc {
   const auto& tree_and_subtrees =
-      imported_trees_and_subtrees_[absolute_node_id.check_ir_id.index]();
+      tree_and_subtrees_getters_[absolute_node_id.check_ir_id.index]();
   return tree_and_subtrees.NodeToDiagnosticLoc(absolute_node_id.node_id,
                                                token_only);
 }
 
-auto SemIRDiagnosticConverter::ConvertArg(llvm::Any arg) const -> llvm::Any {
+auto SemIRLocDiagnosticEmitter::ConvertArg(llvm::Any arg) const -> llvm::Any {
   if (auto* library_name_id = llvm::any_cast<SemIR::LibraryNameId>(&arg)) {
     std::string library_name;
     if (*library_name_id == SemIR::LibraryNameId::Default) {
@@ -118,7 +118,7 @@ auto SemIRDiagnosticConverter::ConvertArg(llvm::Any arg) const -> llvm::Any {
     return llvm::APSInt(typed_int->value,
                         !sem_ir_->types().IsSignedInt(typed_int->type));
   }
-  return DiagnosticConverter<SemIRLoc>::ConvertArg(arg);
+  return DiagnosticEmitter<SemIRLoc>::ConvertArg(arg);
 }
 
 }  // namespace Carbon::Check

+ 20 - 17
toolchain/check/sem_ir_diagnostic_converter.h → toolchain/check/sem_ir_loc_diagnostic_emitter.h

@@ -2,8 +2,8 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-#ifndef CARBON_TOOLCHAIN_CHECK_SEM_IR_DIAGNOSTIC_CONVERTER_H_
-#define CARBON_TOOLCHAIN_CHECK_SEM_IR_DIAGNOSTIC_CONVERTER_H_
+#ifndef CARBON_TOOLCHAIN_CHECK_SEM_IR_LOC_DIAGNOSTIC_EMITTER_H_
+#define CARBON_TOOLCHAIN_CHECK_SEM_IR_LOC_DIAGNOSTIC_EMITTER_H_
 
 #include "llvm/ADT/ArrayRef.h"
 #include "toolchain/check/diagnostic_helpers.h"
@@ -17,14 +17,26 @@
 namespace Carbon::Check {
 
 // Handles the transformation of a SemIRLoc to a DiagnosticLoc.
-class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLoc> {
+class SemIRLocDiagnosticEmitter : public DiagnosticEmitter<SemIRLoc> {
  public:
-  explicit SemIRDiagnosticConverter(
-      llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> imported_trees_and_subtrees,
+  explicit SemIRLocDiagnosticEmitter(
+      DiagnosticConsumer* consumer,
+      llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
       const SemIR::File* sem_ir)
-      : imported_trees_and_subtrees_(imported_trees_and_subtrees),
+      : DiagnosticEmitter(consumer),
+        tree_and_subtrees_getters_(tree_and_subtrees_getters),
         sem_ir_(sem_ir) {}
 
+  // If a byte offset is past the current last byte offset, advances forward.
+  // Earlier offsets are ignored.
+  auto AdvanceToken(Lex::TokenIndex token) -> void {
+    last_token_ = std::max(last_token_, token);
+  }
+
+ protected:
+  // Implements argument conversions for supported check-phase arguments.
+  auto ConvertArg(llvm::Any arg) const -> llvm::Any override;
+
   // Implements `DiagnosticConverter::ConvertLoc`. Adds context for any imports
   // used in the current SemIR to get to the underlying code.
   //
@@ -34,15 +46,6 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLoc> {
   auto ConvertLoc(SemIRLoc loc, ContextFnT context_fn) const
       -> ConvertedDiagnosticLoc override;
 
-  // Implements argument conversions for supported check-phase arguments.
-  auto ConvertArg(llvm::Any arg) const -> llvm::Any override;
-
-  // If a byte offset is past the current last byte offset, advances forward.
-  // Earlier offsets are ignored.
-  auto AdvanceToken(Lex::TokenIndex token) -> void {
-    last_token_ = std::max(last_token_, token);
-  }
-
  private:
   // Implements `ConvertLoc`, but without `last_token_` applied.
   auto ConvertLocImpl(SemIRLoc loc, ContextFnT context_fn) const
@@ -54,7 +57,7 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLoc> {
                         ContextFnT context_fn) const -> ConvertedDiagnosticLoc;
 
   // Converters for each SemIR.
-  llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> imported_trees_and_subtrees_;
+  llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters_;
 
   // The current SemIR being processed.
   const SemIR::File* sem_ir_;
@@ -65,4 +68,4 @@ class SemIRDiagnosticConverter : public DiagnosticConverter<SemIRLoc> {
 
 }  // namespace Carbon::Check
 
-#endif  // CARBON_TOOLCHAIN_CHECK_SEM_IR_DIAGNOSTIC_CONVERTER_H_
+#endif  // CARBON_TOOLCHAIN_CHECK_SEM_IR_LOC_DIAGNOSTIC_EMITTER_H_

+ 29 - 46
toolchain/diagnostics/diagnostic_emitter.h

@@ -41,30 +41,6 @@ struct ConvertedDiagnosticLoc {
   int32_t last_byte_offset;
 };
 
-// An interface that can convert some representation of a location into a
-// diagnostic location.
-template <typename LocT>
-class DiagnosticConverter {
- public:
-  // Callback type used to report context messages from ConvertLoc.
-  // Note that the first parameter type is DiagnosticLoc rather than
-  // LocT, because ConvertLoc must not recurse.
-  using ContextFnT =
-      llvm::function_ref<void(DiagnosticLoc, const DiagnosticBase<>&)>;
-
-  virtual ~DiagnosticConverter() = default;
-
-  // Converts a LocT to a DiagnosticLoc and its `last_byte_offset` (see
-  // `DiagnosticMessage`). ConvertLoc may invoke context_fn to provide context
-  // messages.
-  virtual auto ConvertLoc(LocT loc, ContextFnT context_fn) const
-      -> ConvertedDiagnosticLoc = 0;
-
-  // Converts arg types as needed. Not all uses require conversion, so the
-  // default returns the argument unchanged.
-  virtual auto ConvertArg(llvm::Any arg) const -> llvm::Any { return arg; }
-};
-
 // Used by types to indicate a diagnostic type conversion that results in the
 // provided StorageType. For example, to convert NameId to a std::string, we
 // write:
@@ -155,15 +131,11 @@ class DiagnosticEmitter {
     Diagnostic diagnostic_;
   };
 
-  // The `converter` and `consumer` are required to outlive the diagnostic
-  // emitter.
-  // TODO: Adjust construction to take stored arguments as pointers, and
-  // consider taking `converter` by value (move/copy) instead of reference.
-  explicit DiagnosticEmitter(DiagnosticConverter<LocT>& converter,
-                             DiagnosticConsumer& consumer)
-      : converter_(&converter), consumer_(&consumer) {}
+  // `consumer` is required to outlive the diagnostic emitter.
+  explicit DiagnosticEmitter(DiagnosticConsumer* consumer)
+      : consumer_(consumer) {}
 
-  ~DiagnosticEmitter() = default;
+  virtual ~DiagnosticEmitter() = default;
 
   // Emits an error.
   //
@@ -187,6 +159,23 @@ class DiagnosticEmitter {
   // be silently ignored.
   auto BuildSuppressed() -> DiagnosticBuilder { return DiagnosticBuilder(); }
 
+ protected:
+  // Callback type used to report context messages from ConvertLoc.
+  // Note that the first parameter type is DiagnosticLoc rather than
+  // LocT, because ConvertLoc must not recurse.
+  using ContextFnT =
+      llvm::function_ref<void(DiagnosticLoc, const DiagnosticBase<>&)>;
+
+  // Converts a LocT to a DiagnosticLoc and its `last_byte_offset` (see
+  // `DiagnosticMessage`). ConvertLoc may invoke context_fn to provide context
+  // messages.
+  virtual auto ConvertLoc(LocT loc, ContextFnT context_fn) const
+      -> ConvertedDiagnosticLoc = 0;
+
+  // Converts arg types as needed. Most children don't customize conversion, so
+  // the default returns the argument unchanged.
+  virtual auto ConvertArg(llvm::Any arg) const -> llvm::Any { return arg; }
+
  private:
   // Converts an argument to llvm::Any for storage, handling input to storage
   // type conversion when needed.
@@ -196,7 +185,6 @@ class DiagnosticEmitter {
   template <typename OtherLocT, typename AnnotateFn>
   friend class DiagnosticAnnotationScope;
 
-  DiagnosticConverter<LocT>* converter_;
   DiagnosticConsumer* consumer_;
   llvm::SmallVector<llvm::function_ref<auto(DiagnosticBuilder& builder)->void>>
       annotate_fns_;
@@ -211,9 +199,7 @@ class DiagnosticEmitter {
 // up being more noise than it is worth.
 class NoLocDiagnosticEmitter : public DiagnosticEmitter<void*> {
  public:
-  // This constructor only applies to NoLocDiagnosticEmitter.
-  explicit NoLocDiagnosticEmitter(DiagnosticConsumer* consumer)
-      : DiagnosticEmitter(converter_, *consumer) {}
+  using DiagnosticEmitter::DiagnosticEmitter;
 
   // Emits an error. This specialization only applies to
   // `NoLocDiagnosticEmitter`.
@@ -223,14 +209,11 @@ class NoLocDiagnosticEmitter : public DiagnosticEmitter<void*> {
     DiagnosticEmitter::Emit(nullptr, diagnostic_base, args...);
   }
 
- private:
-  struct Converter : DiagnosticConverter<void*> {
-    auto ConvertLoc(void* /*loc*/, ContextFnT /*context_fn*/) const
-        -> ConvertedDiagnosticLoc override {
-      return {.loc = {.filename = ""}, .last_byte_offset = -1};
-    }
-  };
-  Converter converter_;
+ protected:
+  auto ConvertLoc(void* /*loc*/, ContextFnT /*context_fn*/) const
+      -> ConvertedDiagnosticLoc override {
+    return {.loc = {.filename = ""}, .last_byte_offset = -1};
+  }
 };
 
 // An RAII object that denotes a scope in which any diagnostic produced should
@@ -328,7 +311,7 @@ auto DiagnosticEmitter<LocT>::DiagnosticBuilder::AddMessage(
   if (!emitter_) {
     return;
   }
-  auto converted = emitter_->converter_->ConvertLoc(
+  auto converted = emitter_->ConvertLoc(
       loc, [&](DiagnosticLoc context_loc,
                const DiagnosticBase<>& context_diagnostic_base) {
         AddMessageWithDiagnosticLoc(context_loc, context_diagnostic_base, {});
@@ -397,7 +380,7 @@ auto DiagnosticEmitter<LocT>::Build(
 template <typename LocT>
 template <typename Arg>
 auto DiagnosticEmitter<LocT>::MakeAny(Arg arg) -> llvm::Any {
-  llvm::Any converted = converter_->ConvertArg(arg);
+  llvm::Any converted = ConvertArg(arg);
   using Storage = Internal::DiagnosticTypeForArg<Arg>::StorageType;
   CARBON_CHECK(llvm::any_cast<Storage>(&converted),
                "Failed to convert argument of type {0} to its storage type {1}",

+ 7 - 4
toolchain/diagnostics/diagnostic_emitter_test.cpp

@@ -17,7 +17,11 @@ using ::Carbon::Testing::IsDiagnostic;
 using ::Carbon::Testing::IsSingleDiagnostic;
 using testing::ElementsAre;
 
-struct FakeDiagnosticConverter : DiagnosticConverter<int> {
+class FakeDiagnosticEmitter : public DiagnosticEmitter<int> {
+ public:
+  using DiagnosticEmitter::DiagnosticEmitter;
+
+ protected:
   auto ConvertLoc(int n, ContextFnT /*context_fn*/) const
       -> ConvertedDiagnosticLoc override {
     return {.loc = {.line_number = 1, .column_number = n},
@@ -27,11 +31,10 @@ struct FakeDiagnosticConverter : DiagnosticConverter<int> {
 
 class DiagnosticEmitterTest : public ::testing::Test {
  public:
-  DiagnosticEmitterTest() : emitter_(converter_, consumer_) {}
+  DiagnosticEmitterTest() : emitter_(&consumer_) {}
 
-  FakeDiagnosticConverter converter_;
   Testing::MockDiagnosticConsumer consumer_;
-  DiagnosticEmitter<int> emitter_;
+  FakeDiagnosticEmitter emitter_;
 };
 
 TEST_F(DiagnosticEmitterTest, EmitSimpleError) {

+ 6 - 11
toolchain/diagnostics/file_diagnostics.h

@@ -17,19 +17,14 @@ namespace Carbon {
 // specific emitters must be used for that.
 class FileDiagnosticEmitter : public DiagnosticEmitter<llvm::StringRef> {
  public:
-  explicit FileDiagnosticEmitter(DiagnosticConsumer* consumer)
-      : DiagnosticEmitter<llvm::StringRef>(converter_, *consumer) {}
+  using DiagnosticEmitter::DiagnosticEmitter;
 
- private:
+ protected:
   // Converts a filename directly to the diagnostic location.
-  struct FileDiagnosticConverter : DiagnosticConverter<llvm::StringRef> {
-    auto ConvertLoc(llvm::StringRef filename, ContextFnT /*context_fn*/) const
-        -> ConvertedDiagnosticLoc override {
-      return {.loc = {.filename = filename}, .last_byte_offset = -1};
-    }
-  };
-
-  FileDiagnosticConverter converter_;
+  auto ConvertLoc(llvm::StringRef filename, ContextFnT /*context_fn*/) const
+      -> ConvertedDiagnosticLoc override {
+    return {.loc = {.filename = filename}, .last_byte_offset = -1};
+  }
 };
 
 }  // namespace Carbon

+ 16 - 15
toolchain/diagnostics/null_diagnostics.h

@@ -9,19 +9,7 @@
 
 namespace Carbon {
 
-template <typename LocT>
-inline auto NullDiagnosticConverter() -> DiagnosticConverter<LocT>& {
-  struct Converter : public DiagnosticConverter<LocT> {
-    auto ConvertLoc(LocT /*loc*/,
-                    DiagnosticConverter<LocT>::ContextFnT /*context_fn*/) const
-        -> ConvertedDiagnosticLoc override {
-      return {.loc = {}, .last_byte_offset = -1};
-    }
-  };
-  static auto* converter = new Converter;
-  return *converter;
-}
-
+// Returns a singleton consumer that doesn't print its diagnostics.
 inline auto NullDiagnosticConsumer() -> DiagnosticConsumer& {
   struct Consumer : DiagnosticConsumer {
     auto HandleDiagnostic(Diagnostic /*d*/) -> void override {}
@@ -30,10 +18,23 @@ inline auto NullDiagnosticConsumer() -> DiagnosticConsumer& {
   return *consumer;
 }
 
+// Returns a singleton emitter that doesn't print its diagnostics.
 template <typename LocT>
 inline auto NullDiagnosticEmitter() -> DiagnosticEmitter<LocT>& {
-  static auto* emitter = new DiagnosticEmitter<LocT>(
-      NullDiagnosticConverter<LocT>(), NullDiagnosticConsumer());
+  class Emitter : public DiagnosticEmitter<LocT> {
+   public:
+    using DiagnosticEmitter<LocT>::DiagnosticEmitter;
+
+   protected:
+    // Converts a filename directly to the diagnostic location.
+    auto ConvertLoc(LocT /*loc*/,
+                    DiagnosticEmitter<LocT>::ContextFnT /*context_fn*/) const
+        -> ConvertedDiagnosticLoc override {
+      return {.loc = {}, .last_byte_offset = -1};
+    }
+  };
+
+  static auto* emitter = new Emitter(&NullDiagnosticConsumer());
   return *emitter;
 }
 

+ 6 - 3
toolchain/diagnostics/sorting_diagnostic_consumer_test.cpp

@@ -20,7 +20,11 @@ using ::testing::InSequence;
 
 CARBON_DIAGNOSTIC(TestDiagnostic, Error, "M{0}", int);
 
-struct FakeDiagnosticConverter : DiagnosticConverter<int32_t> {
+class FakeDiagnosticEmitter : public DiagnosticEmitter<int32_t> {
+ public:
+  using DiagnosticEmitter::DiagnosticEmitter;
+
+ protected:
   auto ConvertLoc(int32_t last_byte_offset, ContextFnT /*context_fn*/) const
       -> ConvertedDiagnosticLoc override {
     return {.loc = {}, .last_byte_offset = last_byte_offset};
@@ -28,10 +32,9 @@ struct FakeDiagnosticConverter : DiagnosticConverter<int32_t> {
 };
 
 TEST(SortedDiagnosticEmitterTest, SortErrors) {
-  FakeDiagnosticConverter converter;
   Testing::MockDiagnosticConsumer consumer;
   SortingDiagnosticConsumer sorting_consumer(consumer);
-  DiagnosticEmitter<int32_t> emitter(converter, sorting_consumer);
+  FakeDiagnosticEmitter emitter(&sorting_consumer);
 
   emitter.Emit(1, TestDiagnostic, 1);
   emitter.Emit(-1, TestDiagnostic, 2);

+ 31 - 49
toolchain/driver/compile_subcommand.cpp

@@ -333,22 +333,15 @@ class CompilationUnit {
   // Parses tokens. Returns true on success.
   auto RunParse() -> void;
 
-  // Prepares per-IR lazy fetch functions which may come up in cross-IR
-  // diagnostics.
-  auto PreCheck() -> Parse::GetTreeAndSubtreesFn;
-
   // Returns information needed to check this unit.
-  auto GetCheckUnit(
-      SemIR::CheckIRId check_ir_id,
-      llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> all_trees_and_subtrees)
-      -> Check::Unit;
+  auto GetCheckUnit(SemIR::CheckIRId check_ir_id) -> Check::Unit;
 
   // Runs post-check logic. Returns true if checking succeeded for the IR.
   auto PostCheck() -> void;
 
   // Lower SemIR to LLVM IR.
   auto RunLower(std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-                    all_trees_and_subtrees_for_debug_info) -> void;
+                    tree_and_subtrees_getters_for_debug_info) -> void;
 
   auto RunCodeGen() -> void;
 
@@ -363,6 +356,9 @@ class CompilationUnit {
   auto input_filename() -> llvm::StringRef { return input_filename_; }
   auto success() -> bool { return success_; }
   auto has_source() -> bool { return source_.has_value(); }
+  auto get_trees_and_subtrees() -> Parse::GetTreeAndSubtreesFn {
+    return *tree_and_subtrees_getter_;
+  }
 
  private:
   // Do codegen. Returns true on success.
@@ -414,8 +410,7 @@ class CompilationUnit {
   std::optional<Parse::Tree> parse_tree_;
   std::optional<Parse::TreeAndSubtrees> parse_tree_and_subtrees_;
   std::optional<std::function<const Parse::TreeAndSubtrees&()>>
-      get_parse_tree_and_subtrees_;
-  std::optional<Check::SemIRDiagnosticConverter> sem_ir_converter_;
+      tree_and_subtrees_getter_;
   std::optional<SemIR::File> sem_ir_;
   std::unique_ptr<llvm::LLVMContext> llvm_context_;
   std::unique_ptr<llvm::Module> module_;
@@ -500,36 +495,25 @@ auto CompilationUnit::RunParse() -> void {
   }
 }
 
-auto CompilationUnit::PreCheck() -> Parse::GetTreeAndSubtreesFn {
+auto CompilationUnit::GetCheckUnit(SemIR::CheckIRId check_ir_id)
+    -> Check::Unit {
   CARBON_CHECK(parse_tree_, "Must call RunParse first");
-  CARBON_CHECK(!get_parse_tree_and_subtrees_, "Called PreCheck twice");
+  CARBON_CHECK(!sem_ir_, "Called GetCheckUnit twice");
 
-  get_parse_tree_and_subtrees_ = [this]() -> const Parse::TreeAndSubtrees& {
+  tree_and_subtrees_getter_ = [this]() -> const Parse::TreeAndSubtrees& {
     return this->GetParseTreeAndSubtrees();
   };
-  return *get_parse_tree_and_subtrees_;
-}
-
-auto CompilationUnit::GetCheckUnit(
-    SemIR::CheckIRId check_ir_id,
-    llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> all_trees_and_subtrees)
-    -> Check::Unit {
-  CARBON_CHECK(get_parse_tree_and_subtrees_, "Must call PreCheck first");
-  CARBON_CHECK(!sem_ir_converter_, "Called GetCheckUnit twice");
-
   sem_ir_.emplace(&*parse_tree_, check_ir_id, parse_tree_->packaging_decl(),
                   value_stores_, input_filename_);
-  sem_ir_converter_.emplace(all_trees_and_subtrees, &*sem_ir_);
   return {.consumer = consumer_,
           .value_stores = &value_stores_,
           .timings = timings_ ? &*timings_ : nullptr,
-          .get_parse_tree_and_subtrees = *get_parse_tree_and_subtrees_,
-          .sem_ir = &*sem_ir_,
-          .sem_ir_converter = &*sem_ir_converter_};
+          .tree_and_subtrees_getter = *tree_and_subtrees_getter_,
+          .sem_ir = &*sem_ir_};
 }
 
 auto CompilationUnit::PostCheck() -> void {
-  CARBON_CHECK(sem_ir_converter_, "Must call GetCheckUnit first");
+  CARBON_CHECK(sem_ir_, "Must call GetCheckUnit first");
 
   // We've finished all steps that can produce diagnostics. Emit the
   // diagnostics now, so that the developer sees them sooner and doesn't need
@@ -589,15 +573,15 @@ auto CompilationUnit::PostCheck() -> void {
 
 auto CompilationUnit::RunLower(
     std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-        all_trees_and_subtrees_for_debug_info) -> void {
+        tree_and_subtrees_getters_for_debug_info) -> void {
   LogCall("Lower::LowerToLLVM", "lower", [&] {
     llvm_context_ = std::make_unique<llvm::LLVMContext>();
     // TODO: Consider disabling instruction naming by default if we're not
     // producing textual LLVM IR.
     SemIR::InstNamer inst_namer(&*sem_ir_);
     module_ = Lower::LowerToLLVM(
-        *llvm_context_, all_trees_and_subtrees_for_debug_info, input_filename_,
-        *sem_ir_, &inst_namer, vlog_stream_);
+        *llvm_context_, tree_and_subtrees_getters_for_debug_info,
+        input_filename_, *sem_ir_, &inst_namer, vlog_stream_);
   });
   if (vlog_stream_) {
     CARBON_VLOG("*** llvm::Module ***\n");
@@ -843,25 +827,13 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
     return make_result();
   }
 
-  // Pre-check assigns IR IDs and constructs node converters.
-  llvm::SmallVector<Parse::GetTreeAndSubtreesFn> all_trees_and_subtrees;
-  // This size may not match due to units that are missing source, but that's an
-  // error case and not worth extra work.
-  all_trees_and_subtrees.reserve(units.size());
-  for (auto& unit : units) {
-    if (unit->has_source()) {
-      all_trees_and_subtrees.push_back(unit->PreCheck());
-    }
-  }
-
   // Gather Check::Units.
   llvm::SmallVector<Check::Unit> check_units;
-  check_units.reserve(all_trees_and_subtrees.size());
+  check_units.reserve(units.size());
   for (auto& unit : units) {
     if (unit->has_source()) {
       SemIR::CheckIRId check_ir_id(check_units.size());
-      check_units.push_back(
-          unit->GetCheckUnit(check_ir_id, all_trees_and_subtrees));
+      check_units.push_back(unit->GetCheckUnit(check_ir_id));
     }
   }
 
@@ -888,13 +860,23 @@ auto CompileSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
   }
 
   // Lower.
+  llvm::SmallVector<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters;
   std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-      all_trees_and_subtrees_for_debug_info;
+      tree_and_subtrees_getters_for_debug_info;
   if (options_.include_debug_info) {
-    all_trees_and_subtrees_for_debug_info = all_trees_and_subtrees;
+    // This size may not match due to units that are missing source, but that's
+    // an error case and not worth extra work.
+    tree_and_subtrees_getters.reserve(units.size());
+    for (auto& unit : units) {
+      if (unit->has_source()) {
+        tree_and_subtrees_getters.push_back(unit->get_trees_and_subtrees());
+      }
+    }
+    tree_and_subtrees_getters_for_debug_info = {};
+    tree_and_subtrees_getters_for_debug_info = tree_and_subtrees_getters;
   }
   for (const auto& unit : units) {
-    unit->RunLower(all_trees_and_subtrees_for_debug_info);
+    unit->RunLower(tree_and_subtrees_getters_for_debug_info);
   }
   if (options_.phase == CompileOptions::Phase::Lower) {
     return make_result();

+ 1 - 0
toolchain/lex/BUILD

@@ -188,6 +188,7 @@ cc_library(
         ":helpers",
         ":numeric_literal",
         ":string_literal",
+        ":token_index",
         ":token_kind",
         ":tokenized_buffer",
         "//common:check",

+ 6 - 9
toolchain/lex/lex.cpp

@@ -17,6 +17,7 @@
 #include "toolchain/lex/helpers.h"
 #include "toolchain/lex/numeric_literal.h"
 #include "toolchain/lex/string_literal.h"
+#include "toolchain/lex/token_index.h"
 #include "toolchain/lex/token_kind.h"
 #include "toolchain/lex/tokenized_buffer.h"
 
@@ -83,10 +84,8 @@ class [[clang::internal_linkage]] Lexer {
         DiagnosticConsumer& consumer)
       : buffer_(value_stores, source),
         consumer_(consumer),
-        converter_(&buffer_),
-        emitter_(converter_, consumer_),
-        token_converter_(&buffer_),
-        token_emitter_(token_converter_, consumer_) {}
+        emitter_(&consumer_, &buffer_),
+        token_emitter_(&consumer_, &buffer_) {}
 
   // Find all line endings and create the line data structures.
   //
@@ -223,11 +222,9 @@ class [[clang::internal_linkage]] Lexer {
 
   ErrorTrackingDiagnosticConsumer consumer_;
 
-  TokenizedBuffer::SourceBufferDiagnosticConverter converter_;
-  LexerDiagnosticEmitter emitter_;
+  TokenizedBuffer::SourcePointerDiagnosticEmitter emitter_;
 
-  TokenDiagnosticConverter token_converter_;
-  TokenDiagnosticEmitter token_emitter_;
+  TokenizedBuffer::TokenDiagnosticEmitter token_emitter_;
 };
 
 #if CARBON_USE_SIMD
@@ -1529,7 +1526,7 @@ class Lexer::ErrorRecoveryBuffer {
 };
 
 // Issue an UnmatchedOpening diagnostic.
-static auto DiagnoseUnmatchedOpening(TokenDiagnosticEmitter& emitter,
+static auto DiagnoseUnmatchedOpening(DiagnosticEmitter<TokenIndex>& emitter,
                                      TokenIndex opening_token) -> void {
   CARBON_DIAGNOSTIC(UnmatchedOpening, Error,
                     "opening symbol without a corresponding closing symbol");

+ 2 - 2
toolchain/lex/numeric_literal_benchmark.cpp

@@ -26,7 +26,7 @@ static void BM_Lex_Int(benchmark::State& state) {
 static void BM_ComputeValue_Float(benchmark::State& state) {
   auto val = NumericLiteral::Lex("0.000001", true);
   CARBON_CHECK(val);
-  auto emitter = NullDiagnosticEmitter<const char*>();
+  auto& emitter = NullDiagnosticEmitter<const char*>();
   for (auto _ : state) {
     val->ComputeValue(emitter);
   }
@@ -34,7 +34,7 @@ static void BM_ComputeValue_Float(benchmark::State& state) {
 
 static void BM_ComputeValue_Int(benchmark::State& state) {
   auto val = NumericLiteral::Lex("1_234_567_890", true);
-  auto emitter = NullDiagnosticEmitter<const char*>();
+  auto& emitter = NullDiagnosticEmitter<const char*>();
   CARBON_CHECK(val);
   for (auto _ : state) {
     val->ComputeValue(emitter);

+ 1 - 2
toolchain/lex/numeric_literal_test.cpp

@@ -37,8 +37,7 @@ class NumericLiteralTest : public ::testing::Test {
 
   auto Parse(llvm::StringRef text, bool can_form_real_literal = true)
       -> NumericLiteral::Value {
-    Testing::SingleTokenDiagnosticConverter converter(text);
-    DiagnosticEmitter<const char*> emitter(converter, error_tracker);
+    Testing::SingleTokenDiagnosticEmitter emitter(&error_tracker, text);
     return Lex(text, can_form_real_literal).ComputeValue(emitter);
   }
 

+ 1 - 2
toolchain/lex/string_literal_test.cpp

@@ -27,8 +27,7 @@ class StringLiteralTest : public ::testing::Test {
 
   auto Parse(llvm::StringRef text) -> llvm::StringRef {
     StringLiteral token = Lex(text);
-    Testing::SingleTokenDiagnosticConverter converter(text);
-    DiagnosticEmitter<const char*> emitter(converter, error_tracker_);
+    Testing::SingleTokenDiagnosticEmitter emitter(&error_tracker_, text);
     return token.ComputeValue(allocator_, emitter);
   }
 

+ 5 - 3
toolchain/lex/test_helpers.h

@@ -17,13 +17,15 @@ namespace Carbon::Testing {
 
 // A diagnostic converter for tests that lex a single token. Produces
 // locations such as "`12.5`:1:3" to refer to the third character in the token.
-class SingleTokenDiagnosticConverter : public DiagnosticConverter<const char*> {
+class SingleTokenDiagnosticEmitter : public DiagnosticEmitter<const char*> {
  public:
   // Form a converter for a given token. The string provided here must refer
   // to the same character array that we are going to lex.
-  explicit SingleTokenDiagnosticConverter(llvm::StringRef token)
-      : token_(token) {}
+  explicit SingleTokenDiagnosticEmitter(DiagnosticConsumer* consumer,
+                                        llvm::StringRef token)
+      : DiagnosticEmitter(consumer), token_(token) {}
 
+ protected:
   // Implements `DiagnosticConverter::ConvertLoc`.
   auto ConvertLoc(const char* pos, ContextFnT /*context_fn*/) const
       -> ConvertedDiagnosticLoc override {

+ 0 - 12
toolchain/lex/tokenized_buffer.cpp

@@ -415,12 +415,6 @@ auto TokenizedBuffer::SourcePointerToDiagnosticLoc(const char* loc) const
           .last_byte_offset = offset};
 }
 
-auto TokenizedBuffer::SourceBufferDiagnosticConverter::ConvertLoc(
-    const char* loc, ContextFnT /*context_fn*/) const
-    -> ConvertedDiagnosticLoc {
-  return tokens_->SourcePointerToDiagnosticLoc(loc);
-}
-
 auto TokenizedBuffer::TokenToDiagnosticLoc(TokenIndex token) const
     -> ConvertedDiagnosticLoc {
   // Map the token location into a position within the source buffer.
@@ -435,10 +429,4 @@ auto TokenizedBuffer::TokenToDiagnosticLoc(TokenIndex token) const
   return converted;
 }
 
-auto TokenDiagnosticConverter::ConvertLoc(TokenIndex token,
-                                          ContextFnT /*context_fn*/) const
-    -> ConvertedDiagnosticLoc {
-  return tokens_->TokenToDiagnosticLoc(token);
-}
-
 }  // namespace Carbon::Lex

+ 25 - 34
toolchain/lex/tokenized_buffer.h

@@ -60,24 +60,6 @@ using CommentIterator = IndexIterator<CommentIndex>;
 // Random-access iterator over tokens within the buffer.
 using TokenIterator = IndexIterator<TokenIndex>;
 
-// A diagnostic location converter that maps token locations into source
-// buffer locations.
-class TokenDiagnosticConverter : public DiagnosticConverter<TokenIndex> {
- public:
-  explicit TokenDiagnosticConverter(const TokenizedBuffer* tokens)
-      : tokens_(tokens) {}
-
-  // Implements `DiagnosticConverter::ConvertLoc`.
-  auto ConvertLoc(TokenIndex token, ContextFnT context_fn) const
-      -> ConvertedDiagnosticLoc override;
-
- protected:
-  auto tokens() const -> const TokenizedBuffer& { return *tokens_; }
-
- private:
-  const TokenizedBuffer* tokens_;
-};
-
 // A buffer of tokenized Carbon source code.
 //
 // This is constructed by lexing the source code text into a series of tokens.
@@ -225,17 +207,33 @@ class TokenizedBuffer : public Printable<TokenizedBuffer> {
  private:
   friend class Lexer;
 
-  // A diagnostic location converter that maps token locations into source
-  // buffer locations.
-  class SourceBufferDiagnosticConverter
-      : public DiagnosticConverter<const char*> {
+  class SourcePointerDiagnosticEmitter : public DiagnosticEmitter<const char*> {
    public:
-    explicit SourceBufferDiagnosticConverter(const TokenizedBuffer* tokens)
-        : tokens_(tokens) {}
+    explicit SourcePointerDiagnosticEmitter(DiagnosticConsumer* consumer,
+                                            const TokenizedBuffer* tokens)
+        : DiagnosticEmitter(consumer), tokens_(tokens) {}
+
+   protected:
+    auto ConvertLoc(const char* loc, ContextFnT /*context_fn*/) const
+        -> ConvertedDiagnosticLoc override {
+      return tokens_->SourcePointerToDiagnosticLoc(loc);
+    }
 
-    // Implements `DiagnosticConverter::ConvertLoc`.
-    auto ConvertLoc(const char* loc, ContextFnT context_fn) const
-        -> ConvertedDiagnosticLoc override;
+   private:
+    const TokenizedBuffer* tokens_;
+  };
+
+  class TokenDiagnosticEmitter : public DiagnosticEmitter<TokenIndex> {
+   public:
+    explicit TokenDiagnosticEmitter(DiagnosticConsumer* consumer,
+                                    const TokenizedBuffer* tokens)
+        : DiagnosticEmitter(consumer), tokens_(tokens) {}
+
+   protected:
+    auto ConvertLoc(TokenIndex token, ContextFnT /*context_fn*/) const
+        -> ConvertedDiagnosticLoc override {
+      return tokens_->TokenToDiagnosticLoc(token);
+    }
 
    private:
     const TokenizedBuffer* tokens_;
@@ -493,13 +491,6 @@ class TokenizedBuffer : public Printable<TokenizedBuffer> {
   llvm::BitVector recovery_tokens_;
 };
 
-// A diagnostic emitter that uses positions within a source buffer's text as
-// its source of location information.
-using LexerDiagnosticEmitter = DiagnosticEmitter<const char*>;
-
-// A diagnostic emitter that uses tokens as its source of location information.
-using TokenDiagnosticEmitter = DiagnosticEmitter<TokenIndex>;
-
 inline auto TokenizedBuffer::GetKind(TokenIndex token) const -> TokenKind {
   return GetTokenInfo(token).kind();
 }

+ 2 - 2
toolchain/lower/BUILD

@@ -17,7 +17,7 @@ cc_library(
     hdrs = ["lower.h"],
     deps = [
         ":context",
-        "//toolchain/check:sem_ir_diagnostic_converter",
+        "//toolchain/check:sem_ir_loc_diagnostic_emitter",
         "//toolchain/parse:tree",
         "//toolchain/sem_ir:file",
         "//toolchain/sem_ir:inst_namer",
@@ -50,7 +50,7 @@ cc_library(
         "//common:raw_string_ostream",
         "//common:vlog",
         "//toolchain/base:kind_switch",
-        "//toolchain/check:sem_ir_diagnostic_converter",
+        "//toolchain/check:sem_ir_loc_diagnostic_emitter",
         "//toolchain/sem_ir:absolute_node_id",
         "//toolchain/sem_ir:entry_point",
         "//toolchain/sem_ir:file",

+ 6 - 5
toolchain/lower/file_context.cpp

@@ -26,18 +26,18 @@ namespace Carbon::Lower {
 FileContext::FileContext(
     llvm::LLVMContext& llvm_context,
     std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-        all_trees_and_subtrees_for_debug_info,
+        tree_and_subtrees_getters_for_debug_info,
     llvm::StringRef module_name, const SemIR::File& sem_ir,
     const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream)
     : llvm_context_(&llvm_context),
       llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
       di_builder_(*llvm_module_),
       di_compile_unit_(
-          all_trees_and_subtrees_for_debug_info
+          tree_and_subtrees_getters_for_debug_info
               ? BuildDICompileUnit(module_name, *llvm_module_, di_builder_)
               : nullptr),
-      all_trees_and_subtrees_for_debug_info_(
-          all_trees_and_subtrees_for_debug_info),
+      tree_and_subtrees_getters_for_debug_info_(
+          tree_and_subtrees_getters_for_debug_info),
       sem_ir_(&sem_ir),
       inst_namer_(inst_namer),
       vlog_stream_(vlog_stream) {
@@ -621,7 +621,8 @@ auto FileContext::BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
 auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> LocForDI {
   SemIR::AbsoluteNodeId resolved = GetAbsoluteNodeId(sem_ir_, inst_id).back();
   const auto& tree_and_subtrees =
-      (*all_trees_and_subtrees_for_debug_info_)[resolved.check_ir_id.index]();
+      (*tree_and_subtrees_getters_for_debug_info_)[resolved.check_ir_id
+                                                       .index]();
   const auto& tokens = tree_and_subtrees.tree().tokens();
 
   if (resolved.node_id.has_value()) {

+ 3 - 3
toolchain/lower/file_context.h

@@ -9,7 +9,7 @@
 #include "llvm/IR/DIBuilder.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
-#include "toolchain/check/sem_ir_diagnostic_converter.h"
+#include "toolchain/check/sem_ir_loc_diagnostic_emitter.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/inst_namer.h"
 
@@ -30,7 +30,7 @@ class FileContext {
   explicit FileContext(
       llvm::LLVMContext& llvm_context,
       std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-          all_trees_and_subtrees_for_debug_info,
+          tree_and_subtrees_getters_for_debug_info,
       llvm::StringRef module_name, const SemIR::File& sem_ir,
       const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream);
 
@@ -131,7 +131,7 @@ class FileContext {
 
   // The trees are only provided when debug info should be emitted.
   std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-      all_trees_and_subtrees_for_debug_info_;
+      tree_and_subtrees_getters_for_debug_info_;
 
   // The input SemIR.
   const SemIR::File* const sem_ir_;

+ 2 - 2
toolchain/lower/lower.cpp

@@ -10,12 +10,12 @@ namespace Carbon::Lower {
 
 auto LowerToLLVM(llvm::LLVMContext& llvm_context,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-                     all_trees_and_subtrees_for_debug_info,
+                     tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,
                  const SemIR::InstNamer* inst_namer,
                  llvm::raw_ostream* vlog_stream)
     -> std::unique_ptr<llvm::Module> {
-  FileContext context(llvm_context, all_trees_and_subtrees_for_debug_info,
+  FileContext context(llvm_context, tree_and_subtrees_getters_for_debug_info,
                       module_name, sem_ir, inst_namer, vlog_stream);
   return context.Run();
 }

+ 1 - 1
toolchain/lower/lower.h

@@ -17,7 +17,7 @@ namespace Carbon::Lower {
 // Lowers SemIR to LLVM IR.
 auto LowerToLLVM(llvm::LLVMContext& llvm_context,
                  std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
-                     all_trees_and_subtrees_for_debug_info,
+                     tree_and_subtrees_getters_for_debug_info,
                  llvm::StringRef module_name, const SemIR::File& sem_ir,
                  const SemIR::InstNamer* inst_namer,
                  llvm::raw_ostream* vlog_stream)

+ 2 - 2
toolchain/parse/context.cpp

@@ -9,6 +9,7 @@
 #include "common/check.h"
 #include "common/ostream.h"
 #include "llvm/ADT/STLExtras.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/lex/token_kind.h"
 #include "toolchain/lex/tokenized_buffer.h"
@@ -24,9 +25,8 @@ Context::Context(Tree* tree, Lex::TokenizedBuffer* tokens,
                  DiagnosticConsumer* consumer, llvm::raw_ostream* vlog_stream)
     : tree_(tree),
       tokens_(tokens),
-      converter_(tokens, &position_),
       err_tracker_(*consumer),
-      emitter_(converter_, err_tracker_),
+      emitter_(&err_tracker_, this),
       vlog_stream_(vlog_stream),
       position_(tokens_->tokens().begin()),
       end_(tokens_->tokens().end()) {

+ 24 - 27
toolchain/parse/context.h

@@ -33,6 +33,28 @@ enum class Lookahead : int32_t {
 // documentation.
 class Context {
  public:
+  // A token-based emitter for use during parse.
+  class Emitter : public DiagnosticEmitter<Lex::TokenIndex> {
+   public:
+    explicit Emitter(DiagnosticConsumer* consumer, Context* context)
+        : DiagnosticEmitter(consumer), context_(context) {}
+
+   protected:
+    // Applies the `position_` to the `last_byte_offset` returned by
+    // `TokenToDiagnosticLoc`.
+    auto ConvertLoc(Lex::TokenIndex token, ContextFnT /*context_fn*/) const
+        -> ConvertedDiagnosticLoc override {
+      auto converted = context_->tokens().TokenToDiagnosticLoc(token);
+      converted.last_byte_offset =
+          std::max(converted.last_byte_offset,
+                   context_->tokens().GetByteOffset(*context_->position()));
+      return converted;
+    }
+
+   private:
+    Context* context_;
+  };
+
   // Possible operator fixities for errors.
   enum class OperatorFixity : int8_t { Prefix, Infix, Postfix };
 
@@ -376,7 +398,7 @@ class Context {
 
   auto has_errors() const -> bool { return err_tracker_.seen_error(); }
 
-  auto emitter() -> Lex::TokenDiagnosticEmitter& { return emitter_; }
+  auto emitter() -> Emitter& { return emitter_; }
 
   auto position() -> Lex::TokenIterator& { return position_; }
   auto position() const -> Lex::TokenIterator { return position_; }
@@ -402,30 +424,6 @@ class Context {
   }
 
  private:
-  // Applies the `position_` to the `last_byte_offset` returned by
-  // `TokenToDiagnosticLoc`.
-  class TokenDiagnosticConverterForParse
-      : public DiagnosticConverter<Lex::TokenIndex> {
-   public:
-    explicit TokenDiagnosticConverterForParse(Lex::TokenizedBuffer* tokens,
-                                              Lex::TokenIterator* position)
-        : tokens_(tokens), position_(position) {}
-
-    auto ConvertLoc(Lex::TokenIndex token, ContextFnT /*context_fn*/) const
-        -> ConvertedDiagnosticLoc override {
-      auto converted = tokens_->TokenToDiagnosticLoc(token);
-      converted.last_byte_offset = std::max(
-          converted.last_byte_offset, tokens_->GetByteOffset(**position_));
-      return converted;
-    }
-
-   private:
-    Lex::TokenizedBuffer* tokens_;
-
-    // The position in `Parse()`.
-    Lex::TokenIterator* position_;
-  };
-
   // Prints a single token for a stack dump. Used by PrintForStackDump.
   auto PrintTokenForStackDump(llvm::raw_ostream& output,
                               Lex::TokenIndex token) const -> void;
@@ -433,9 +431,8 @@ class Context {
   Tree* tree_;
   Lex::TokenizedBuffer* tokens_;
 
-  TokenDiagnosticConverterForParse converter_;
   ErrorTrackingDiagnosticConsumer err_tracker_;
-  Lex::TokenDiagnosticEmitter emitter_;
+  Emitter emitter_;
 
   // Whether to print verbose output.
   llvm::raw_ostream* vlog_stream_;