Explorar o código

Refactor diagnostic_emitter.h into multiple files. (#3800)

This is to make it easier to locate various diagnostic types. I was
thinking to separately rename DiagnosticLocationTranslator to
DiagnosticTranslator, because it's now handling some type translations.

This is mostly keeping classes in one place, but FormatFn handling is
moved from DiagnosticMessage to DiagnosticEmitter for a cleaner
dependency graph where DiagnosticTypeForArg is concerned.
Jon Ross-Perkins %!s(int64=2) %!d(string=hai) anos
pai
achega
8907948242

+ 8 - 1
toolchain/diagnostics/BUILD

@@ -13,10 +13,17 @@ filegroup(
 
 cc_library(
     name = "diagnostic_emitter",
-    hdrs = ["diagnostic_emitter.h"],
+    srcs = ["diagnostic_consumer.cpp"],
+    hdrs = [
+        "diagnostic.h",
+        "diagnostic_consumer.h",
+        "diagnostic_emitter.h",
+        "diagnostic_translator.h",
+    ],
     deps = [
         ":diagnostic_kind",
         "//common:check",
+        "//common:ostream",
         "@llvm-project//llvm:Support",
     ],
 )

+ 135 - 0
toolchain/diagnostics/diagnostic.h

@@ -0,0 +1,135 @@
+// 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_DIAGNOSTICS_DIAGNOSTIC_H_
+#define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_H_
+
+#include <cstdint>
+#include <functional>
+#include <string>
+
+#include "common/check.h"
+#include "llvm/ADT/Any.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "toolchain/diagnostics/diagnostic_kind.h"
+
+namespace Carbon {
+
+enum class DiagnosticLevel : int8_t {
+  // A note, not indicating an error on its own, but possibly providing context
+  // for an error.
+  Note,
+  // A warning diagnostic, indicating a likely problem with the program.
+  Warning,
+  // An error diagnostic, indicating that the program is not valid.
+  Error,
+};
+
+// Provides a definition of a diagnostic. For example:
+//   CARBON_DIAGNOSTIC(MyDiagnostic, Error, "Invalid code!");
+//   CARBON_DIAGNOSTIC(MyDiagnostic, Warning, "Found {0}, expected {1}.",
+//                     std::string, std::string);
+//
+// Arguments are passed to llvm::formatv; see:
+// https://llvm.org/doxygen/FormatVariadic_8h_source.html
+//
+// See `DiagnosticEmitter::Emit` for comments about argument lifetimes.
+#define CARBON_DIAGNOSTIC(DiagnosticName, Level, Format, ...) \
+  static constexpr auto DiagnosticName =                      \
+      ::Carbon::Internal::DiagnosticBase<__VA_ARGS__>(        \
+          ::Carbon::DiagnosticKind::DiagnosticName,           \
+          ::Carbon::DiagnosticLevel::Level, Format)
+
+// A location for a diagnostic in a file. The lifetime of a DiagnosticLocation
+// is required to be less than SourceBuffer that it refers to due to the
+// contained filename and line references.
+struct DiagnosticLocation {
+  // Name of the file or buffer that this diagnostic refers to.
+  llvm::StringRef filename;
+  // A reference to the line of the error.
+  llvm::StringRef line;
+  // 1-based line number.
+  int32_t line_number = -1;
+  // 1-based column number.
+  int32_t column_number = -1;
+  // A location can represent a range of text if set to >1 value.
+  int32_t length = 1;
+};
+
+// A message composing a diagnostic. This may be the main message, but can also
+// be notes providing more information.
+struct DiagnosticMessage {
+  explicit DiagnosticMessage(
+      DiagnosticKind kind, DiagnosticLocation location,
+      llvm::StringLiteral format, llvm::SmallVector<llvm::Any> format_args,
+      std::function<std::string(const DiagnosticMessage&)> format_fn)
+      : kind(kind),
+        location(location),
+        format(format),
+        format_args(std::move(format_args)),
+        format_fn(std::move(format_fn)) {}
+
+  // The diagnostic's kind.
+  DiagnosticKind kind;
+
+  // The calculated location of the diagnostic.
+  DiagnosticLocation location;
+
+  // The diagnostic's format string. This, along with format_args, will be
+  // passed to format_fn.
+  llvm::StringLiteral format;
+
+  // A list of format arguments.
+  //
+  // These may be used by non-standard consumers to inspect diagnostic details
+  // without needing to parse the formatted string; however, it should be
+  // understood that diagnostic formats are subject to change and the llvm::Any
+  // offers limited compile-time type safety. Integration tests are required.
+  llvm::SmallVector<llvm::Any> format_args;
+
+  // Returns the formatted string. By default, this uses llvm::formatv.
+  std::function<std::string(const DiagnosticMessage&)> format_fn;
+};
+
+// An instance of a single error or warning.  Information about the diagnostic
+// can be recorded into it for more complex consumers.
+struct Diagnostic {
+  // The diagnostic's level.
+  DiagnosticLevel level;
+
+  // The main error or warning.
+  DiagnosticMessage message;
+
+  // Notes that add context or supplemental information to the diagnostic.
+  llvm::SmallVector<DiagnosticMessage> notes;
+};
+
+namespace Internal {
+
+// Use the DIAGNOSTIC macro to instantiate this.
+// This stores static information about a diagnostic category.
+template <typename... Args>
+struct DiagnosticBase {
+  explicit constexpr DiagnosticBase(DiagnosticKind kind, DiagnosticLevel level,
+                                    llvm::StringLiteral format)
+      : Kind(kind), Level(level), Format(format) {
+    static_assert((... && !std::is_same_v<Args, llvm::StringRef>),
+                  "Use std::string or llvm::StringLiteral for diagnostics to "
+                  "avoid lifetime issues.");
+  }
+
+  // The diagnostic's kind.
+  DiagnosticKind Kind;
+  // The diagnostic's level.
+  DiagnosticLevel Level;
+  // The diagnostic's format for llvm::formatv.
+  llvm::StringLiteral Format;
+};
+
+}  // namespace Internal
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_H_

+ 57 - 0
toolchain/diagnostics/diagnostic_consumer.cpp

@@ -0,0 +1,57 @@
+// 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/diagnostics/diagnostic_consumer.h"
+
+#include <algorithm>
+#include <cstdint>
+
+namespace Carbon {
+
+auto StreamDiagnosticConsumer::HandleDiagnostic(Diagnostic diagnostic) -> void {
+  std::string prefix;
+  if (diagnostic.level == DiagnosticLevel::Error) {
+    prefix = "ERROR: ";
+  }
+  Print(diagnostic.message, prefix);
+  for (const auto& note : diagnostic.notes) {
+    Print(note, "");
+  }
+}
+
+auto StreamDiagnosticConsumer::Print(const DiagnosticMessage& message,
+                                     llvm::StringRef prefix) -> void {
+  *stream_ << message.location.filename;
+  if (message.location.line_number > 0) {
+    *stream_ << ":" << message.location.line_number;
+    if (message.location.column_number > 0) {
+      *stream_ << ":" << message.location.column_number;
+    }
+  }
+  *stream_ << ": " << prefix << message.format_fn(message) << "\n";
+  if (message.location.column_number > 0) {
+    *stream_ << message.location.line << "\n";
+    stream_->indent(message.location.column_number - 1);
+    *stream_ << "^";
+    int underline_length = std::max(0, message.location.length - 1);
+    // We want to ensure that we don't underline past the end of the line in
+    // case of a multiline token.
+    // TODO: revisit this once we can reference multiple ranges on multiple
+    // lines in a single diagnostic message.
+    underline_length = std::min(
+        underline_length, static_cast<int32_t>(message.location.line.size()) -
+                              message.location.column_number);
+    for (int i = 0; i < underline_length; ++i) {
+      *stream_ << "~";
+    }
+    *stream_ << "\n";
+  }
+}
+
+auto ConsoleDiagnosticConsumer() -> DiagnosticConsumer& {
+  static auto* consumer = new StreamDiagnosticConsumer(llvm::errs());
+  return *consumer;
+}
+
+}  // namespace Carbon

+ 81 - 0
toolchain/diagnostics/diagnostic_consumer.h

@@ -0,0 +1,81 @@
+// 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_DIAGNOSTICS_DIAGNOSTIC_CONSUMER_H_
+#define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_CONSUMER_H_
+
+#include "common/ostream.h"
+#include "llvm/ADT/StringRef.h"
+#include "toolchain/diagnostics/diagnostic.h"
+
+namespace Carbon {
+
+// Receives diagnostics as they are emitted.
+class DiagnosticConsumer {
+ public:
+  virtual ~DiagnosticConsumer() = default;
+
+  // Handle a diagnostic.
+  //
+  // This relies on moves of the Diagnostic. At present, diagnostics are
+  // allocated on the stack, so their lifetime is that of HandleDiagnostic.
+  // However, SortingDiagnosticConsumer needs a longer lifetime, until all
+  // diagnostics have been produced. As a consequence, it needs to either copy
+  // or move the Diagnostic, and right now we're moving due to the overhead of
+  // notes.
+  //
+  // At present, there is no persistent storage of diagnostics because IDEs
+  // would be fine with diagnostics being printed immediately and discarded,
+  // without SortingDiagnosticConsumer. If this becomes a performance issue, we
+  // may want to investigate alternative ownership models that address both IDE
+  // and CLI user needs.
+  virtual auto HandleDiagnostic(Diagnostic diagnostic) -> void = 0;
+
+  // Flushes any buffered input.
+  virtual auto Flush() -> void {}
+};
+
+// A diagnostic consumer that prints to a stream.
+class StreamDiagnosticConsumer : public DiagnosticConsumer {
+ public:
+  explicit StreamDiagnosticConsumer(llvm::raw_ostream& stream)
+      : stream_(&stream) {}
+
+  auto HandleDiagnostic(Diagnostic diagnostic) -> void override;
+
+ private:
+  auto Print(const DiagnosticMessage& message, llvm::StringRef prefix) -> void;
+
+  llvm::raw_ostream* stream_;
+};
+
+// Returns a diagnostic consumer instance that prints to stderr.
+auto ConsoleDiagnosticConsumer() -> DiagnosticConsumer&;
+
+// Diagnostic consumer adaptor that tracks whether any errors have been
+// produced.
+class ErrorTrackingDiagnosticConsumer : public DiagnosticConsumer {
+ public:
+  explicit ErrorTrackingDiagnosticConsumer(DiagnosticConsumer& next_consumer)
+      : next_consumer_(&next_consumer) {}
+
+  auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
+    seen_error_ |= diagnostic.level == DiagnosticLevel::Error;
+    next_consumer_->HandleDiagnostic(std::move(diagnostic));
+  }
+
+  // Reset whether we've seen an error.
+  auto Reset() -> void { seen_error_ = false; }
+
+  // Returns whether we've seen an error since the last reset.
+  auto seen_error() const -> bool { return seen_error_; }
+
+ private:
+  DiagnosticConsumer* next_consumer_;
+  bool seen_error_ = false;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_CONSUMER_H_

+ 22 - 288
toolchain/diagnostics/diagnostic_emitter.h

@@ -5,9 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
 #define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
 
-#include <algorithm>
 #include <cstdint>
-#include <functional>
 #include <string>
 #include <type_traits>
 #include <utility>
@@ -15,221 +13,16 @@
 #include "common/check.h"
 #include "llvm/ADT/Any.h"
 #include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/FormatVariadic.h"
-#include "llvm/Support/raw_ostream.h"
+#include "toolchain/diagnostics/diagnostic.h"
+#include "toolchain/diagnostics/diagnostic_consumer.h"
 #include "toolchain/diagnostics/diagnostic_kind.h"
+#include "toolchain/diagnostics/diagnostic_translator.h"
 
 namespace Carbon {
 
-enum class DiagnosticLevel : int8_t {
-  // A note, not indicating an error on its own, but possibly providing context
-  // for an error.
-  Note,
-  // A warning diagnostic, indicating a likely problem with the program.
-  Warning,
-  // An error diagnostic, indicating that the program is not valid.
-  Error,
-};
-
-// Provides a definition of a diagnostic. For example:
-//   CARBON_DIAGNOSTIC(MyDiagnostic, Error, "Invalid code!");
-//   CARBON_DIAGNOSTIC(MyDiagnostic, Warning, "Found {0}, expected {1}.",
-//                     std::string, std::string);
-//
-// Arguments are passed to llvm::formatv; see:
-// https://llvm.org/doxygen/FormatVariadic_8h_source.html
-//
-// See `DiagnosticEmitter::Emit` for comments about argument lifetimes.
-#define CARBON_DIAGNOSTIC(DiagnosticName, Level, Format, ...) \
-  static constexpr auto DiagnosticName =                      \
-      ::Carbon::Internal::DiagnosticBase<__VA_ARGS__>(        \
-          ::Carbon::DiagnosticKind::DiagnosticName,           \
-          ::Carbon::DiagnosticLevel::Level, Format)
-
-// A location for a diagnostic in a file. The lifetime of a DiagnosticLocation
-// is required to be less than SourceBuffer that it refers to due to the
-// contained filename and line references.
-struct DiagnosticLocation {
-  // Name of the file or buffer that this diagnostic refers to.
-  llvm::StringRef filename;
-  // A reference to the line of the error.
-  llvm::StringRef line;
-  // 1-based line number.
-  int32_t line_number = -1;
-  // 1-based column number.
-  int32_t column_number = -1;
-  // A location can represent a range of text if set to >1 value.
-  int32_t length = 1;
-};
-
-// A message composing a diagnostic. This may be the main message, but can also
-// be notes providing more information.
-struct DiagnosticMessage {
-  explicit DiagnosticMessage(
-      DiagnosticKind kind, DiagnosticLocation location,
-      llvm::StringLiteral format, llvm::SmallVector<llvm::Any> format_args,
-      std::function<std::string(const DiagnosticMessage&)> format_fn)
-      : kind(kind),
-        location(location),
-        format(format),
-        format_args(std::move(format_args)),
-        format_fn(std::move(format_fn)) {}
-
-  // The diagnostic's kind.
-  DiagnosticKind kind;
-
-  // The calculated location of the diagnostic.
-  DiagnosticLocation location;
-
-  // The diagnostic's format string. This, along with format_args, will be
-  // passed to format_fn.
-  llvm::StringLiteral format;
-
-  // A list of format arguments.
-  //
-  // These may be used by non-standard consumers to inspect diagnostic details
-  // without needing to parse the formatted string; however, it should be
-  // understood that diagnostic formats are subject to change and the llvm::Any
-  // offers limited compile-time type safety. Integration tests are required.
-  llvm::SmallVector<llvm::Any> format_args;
-
-  // Returns the formatted string. By default, this uses llvm::formatv.
-  std::function<std::string(const DiagnosticMessage&)> format_fn;
-};
-
-// An instance of a single error or warning.  Information about the diagnostic
-// can be recorded into it for more complex consumers.
-struct Diagnostic {
-  // The diagnostic's level.
-  DiagnosticLevel level;
-
-  // The main error or warning.
-  DiagnosticMessage message;
-
-  // Notes that add context or supplemental information to the diagnostic.
-  llvm::SmallVector<DiagnosticMessage> notes;
-};
-
-// Receives diagnostics as they are emitted.
-class DiagnosticConsumer {
- public:
-  virtual ~DiagnosticConsumer() = default;
-
-  // Handle a diagnostic.
-  //
-  // This relies on moves of the Diagnostic. At present, diagnostics are
-  // allocated on the stack, so their lifetime is that of HandleDiagnostic.
-  // However, SortingDiagnosticConsumer needs a longer lifetime, until all
-  // diagnostics have been produced. As a consequence, it needs to either copy
-  // or move the Diagnostic, and right now we're moving due to the overhead of
-  // notes.
-  //
-  // At present, there is no persistent storage of diagnostics because IDEs
-  // would be fine with diagnostics being printed immediately and discarded,
-  // without SortingDiagnosticConsumer. If this becomes a performance issue, we
-  // may want to investigate alternative ownership models that address both IDE
-  // and CLI user needs.
-  virtual auto HandleDiagnostic(Diagnostic diagnostic) -> void = 0;
-
-  // Flushes any buffered input.
-  virtual auto Flush() -> void {}
-};
-
-// Known diagnostic type translations. These are enumerated because `llvm::Any`
-// doesn't expose the contained type; instead, we infer it from a given
-// diagnostic.
-enum class DiagnosticTypeTranslation : int8_t {
-  None,
-  NameId,
-  TypeId,
-};
-
-// An interface that can translate some representation of a location into a
-// diagnostic location.
-//
-// TODO: Revisit this once the diagnostics machinery is more complete and see
-// if we can turn it into a `std::function`.
-template <typename LocationT>
-class DiagnosticLocationTranslator {
- public:
-  virtual ~DiagnosticLocationTranslator() = default;
-
-  virtual auto GetLocation(LocationT loc) -> DiagnosticLocation = 0;
-
-  // Translates arg types as needed. Not all uses support translation, so the
-  // default simply errors.
-  virtual auto TranslateArg(DiagnosticTypeTranslation translation,
-                            llvm::Any /*arg*/) const -> llvm::Any {
-    CARBON_FATAL() << "Unexpected call to TranslateArg: "
-                   << static_cast<int8_t>(translation);
-  }
-};
-
-template <typename StorageTypeT, DiagnosticTypeTranslation TranslationV>
-struct DiagnosticTypeInfo {
-  using StorageType = StorageTypeT;
-  static constexpr DiagnosticTypeTranslation Translation = TranslationV;
-};
-
 namespace Internal {
 
-// Determines whether there's a DiagnosticType member on Arg.
-template <typename Arg>
-concept HasDiagnosticType =
-    requires { std::type_identity<typename Arg::DiagnosticType>(); };
-
-// The default implementation with no translation.
-template <typename Arg, typename /*Unused*/ = void>
-struct DiagnosticTypeForArg
-    : public DiagnosticTypeInfo<Arg, DiagnosticTypeTranslation::None> {};
-
-// Exposes a custom translation for an argument type.
-template <typename Arg>
-  requires HasDiagnosticType<Arg>
-struct DiagnosticTypeForArg<Arg> : public Arg::DiagnosticType {};
-
-// Use the DIAGNOSTIC macro to instantiate this.
-// This stores static information about a diagnostic category.
-template <typename... Args>
-struct DiagnosticBase {
-  explicit constexpr DiagnosticBase(DiagnosticKind kind, DiagnosticLevel level,
-                                    llvm::StringLiteral format)
-      : Kind(kind), Level(level), Format(format) {
-    static_assert((... && !std::is_same_v<Args, llvm::StringRef>),
-                  "Use std::string or llvm::StringLiteral for diagnostics to "
-                  "avoid lifetime issues.");
-  }
-
-  // Calls formatv with the diagnostic's arguments.
-  auto FormatFn(const DiagnosticMessage& message) const -> std::string {
-    return FormatFnImpl(message, std::make_index_sequence<sizeof...(Args)>());
-  };
-
-  // The diagnostic's kind.
-  DiagnosticKind Kind;
-  // The diagnostic's level.
-  DiagnosticLevel Level;
-  // The diagnostic's format for llvm::formatv.
-  llvm::StringLiteral Format;
-
- private:
-  // Handles the cast of llvm::Any to Args types for formatv.
-  // TODO: Custom formatting can be provided with an format_provider, but that
-  // affects all formatv calls. Consider replacing formatv with a custom call
-  // that allows diagnostic-specific formatting.
-  template <std::size_t... N>
-  inline auto FormatFnImpl(const DiagnosticMessage& message,
-                           std::index_sequence<N...> /*indices*/) const
-      -> std::string {
-    CARBON_CHECK(message.format_args.size() == sizeof...(Args));
-    return llvm::formatv(
-        message.format.data(),
-        llvm::any_cast<typename DiagnosticTypeForArg<Args>::StorageType>(
-            message.format_args[N])...);
-  }
-};
-
 // Disable type deduction based on `args`; the type of `diagnostic_base`
 // determines the diagnostic's parameter types.
 template <typename Arg>
@@ -312,11 +105,28 @@ class DiagnosticEmitter {
       return DiagnosticMessage(
           diagnostic_base.Kind, emitter->translator_->GetLocation(location),
           diagnostic_base.Format, std::move(args),
-          [&diagnostic_base](const DiagnosticMessage& message) -> std::string {
-            return diagnostic_base.FormatFn(message);
+          [](const DiagnosticMessage& message) -> std::string {
+            return FormatFn<Args...>(
+                message, std::make_index_sequence<sizeof...(Args)>());
           });
     }
 
+    // Handles the cast of llvm::Any to Args types for formatv.
+    // TODO: Custom formatting can be provided with an format_provider, but that
+    // affects all formatv calls. Consider replacing formatv with a custom call
+    // that allows diagnostic-specific formatting.
+    template <typename... Args, std::size_t... N>
+    static auto FormatFn(const DiagnosticMessage& message,
+                         std::index_sequence<N...> /*indices*/) -> std::string {
+      static_assert(sizeof...(Args) == sizeof...(N), "Invalid template args");
+      CARBON_CHECK(message.format_args.size() == sizeof...(Args));
+      return llvm::formatv(
+          message.format.data(),
+          llvm::any_cast<
+              typename Internal::DiagnosticTypeForArg<Args>::StorageType>(
+              message.format_args[N])...);
+    }
+
     DiagnosticEmitter<LocationT>* emitter_;
     Diagnostic diagnostic_;
   };
@@ -378,82 +188,6 @@ class DiagnosticEmitter {
       annotate_fns_;
 };
 
-class StreamDiagnosticConsumer : public DiagnosticConsumer {
- public:
-  explicit StreamDiagnosticConsumer(llvm::raw_ostream& stream)
-      : stream_(&stream) {}
-
-  auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
-    std::string prefix;
-    if (diagnostic.level == DiagnosticLevel::Error) {
-      prefix = "ERROR: ";
-    }
-    Print(diagnostic.message, prefix);
-    for (const auto& note : diagnostic.notes) {
-      Print(note);
-    }
-  }
-  auto Print(const DiagnosticMessage& message, llvm::StringRef prefix = "")
-      -> void {
-    *stream_ << message.location.filename;
-    if (message.location.line_number > 0) {
-      *stream_ << ":" << message.location.line_number;
-      if (message.location.column_number > 0) {
-        *stream_ << ":" << message.location.column_number;
-      }
-    }
-    *stream_ << ": " << prefix << message.format_fn(message) << "\n";
-    if (message.location.column_number > 0) {
-      *stream_ << message.location.line << "\n";
-      stream_->indent(message.location.column_number - 1);
-      *stream_ << "^";
-      int underline_length = std::max(0, message.location.length - 1);
-      // We want to ensure that we don't underline past the end of the line in
-      // case of a multiline token.
-      // TODO: revisit this once we can reference multiple ranges on multiple
-      // lines in a single diagnostic message.
-      underline_length = std::min(
-          underline_length, static_cast<int32_t>(message.location.line.size()) -
-                                message.location.column_number);
-      for (int i = 0; i < underline_length; ++i) {
-        *stream_ << "~";
-      }
-      *stream_ << "\n";
-    }
-  }
-
- private:
-  llvm::raw_ostream* stream_;
-};
-
-inline auto ConsoleDiagnosticConsumer() -> DiagnosticConsumer& {
-  static auto* consumer = new StreamDiagnosticConsumer(llvm::errs());
-  return *consumer;
-}
-
-// Diagnostic consumer adaptor that tracks whether any errors have been
-// produced.
-class ErrorTrackingDiagnosticConsumer : public DiagnosticConsumer {
- public:
-  explicit ErrorTrackingDiagnosticConsumer(DiagnosticConsumer& next_consumer)
-      : next_consumer_(&next_consumer) {}
-
-  auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
-    seen_error_ |= diagnostic.level == DiagnosticLevel::Error;
-    next_consumer_->HandleDiagnostic(std::move(diagnostic));
-  }
-
-  // Reset whether we've seen an error.
-  auto Reset() -> void { seen_error_ = false; }
-
-  // Returns whether we've seen an error since the last reset.
-  auto seen_error() const -> bool { return seen_error_; }
-
- private:
-  DiagnosticConsumer* next_consumer_;
-  bool seen_error_ = false;
-};
-
 // An RAII object that denotes a scope in which any diagnostic produced should
 // be annotated in some way.
 //

+ 82 - 0
toolchain/diagnostics/diagnostic_translator.h

@@ -0,0 +1,82 @@
+// 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_DIAGNOSTICS_DIAGNOSTIC_TRANSLATOR_H_
+#define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_TRANSLATOR_H_
+
+#include <cstdint>
+
+#include "common/check.h"
+#include "llvm/ADT/Any.h"
+#include "toolchain/diagnostics/diagnostic.h"
+
+namespace Carbon {
+
+// Known diagnostic type translations. These are enumerated because `llvm::Any`
+// doesn't expose the contained type; instead, we infer it from a given
+// diagnostic.
+enum class DiagnosticTypeTranslation : int8_t {
+  None,
+  NameId,
+  TypeId,
+};
+
+// An interface that can translate some representation of a location into a
+// diagnostic location.
+//
+// TODO: Revisit this once the diagnostics machinery is more complete and see
+// if we can turn it into a `std::function`.
+template <typename LocationT>
+class DiagnosticLocationTranslator {
+ public:
+  virtual ~DiagnosticLocationTranslator() = default;
+
+  virtual auto GetLocation(LocationT loc) -> DiagnosticLocation = 0;
+
+  // Translates arg types as needed. Not all uses support translation, so the
+  // default simply errors.
+  virtual auto TranslateArg(DiagnosticTypeTranslation translation,
+                            llvm::Any /*arg*/) const -> llvm::Any {
+    CARBON_FATAL() << "Unexpected call to TranslateArg: "
+                   << static_cast<int8_t>(translation);
+  }
+};
+
+// Used by types to indicate a DiagnosticTypeTranslation that results in the
+// provided StorageType. For example, to translate NameId to a std::string, we
+// write:
+//
+// struct NameId {
+//   using DiagnosticType =
+//       DiagnosticTypeInfo<std::string, DiagnosticTypeTranslation::NameId>;
+// };
+template <typename StorageTypeT, DiagnosticTypeTranslation TranslationV>
+struct DiagnosticTypeInfo {
+  using StorageType = StorageTypeT;
+  static constexpr DiagnosticTypeTranslation Translation = TranslationV;
+};
+
+namespace Internal {
+
+// Determines whether there's a DiagnosticType member on Arg.
+// Used by DiagnosticEmitter.
+template <typename Arg>
+concept HasDiagnosticType =
+    requires { std::type_identity<typename Arg::DiagnosticType>(); };
+
+// The default implementation with no translation.
+template <typename Arg, typename /*Unused*/ = void>
+struct DiagnosticTypeForArg
+    : public DiagnosticTypeInfo<Arg, DiagnosticTypeTranslation::None> {};
+
+// Exposes a custom translation for an argument type.
+template <typename Arg>
+  requires HasDiagnosticType<Arg>
+struct DiagnosticTypeForArg<Arg> : public Arg::DiagnosticType {};
+
+}  // namespace Internal
+
+}  // namespace Carbon
+
+#endif  // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_TRANSLATOR_H_