diagnostic_emitter.h 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #ifndef TOOLCHAIN_DIAGNOSTICS_DIAGNOSTICEMITTER_H_
  5. #define TOOLCHAIN_DIAGNOSTICS_DIAGNOSTICEMITTER_H_
  6. #include <functional>
  7. #include <string>
  8. #include <type_traits>
  9. #include "llvm/ADT/Any.h"
  10. #include "llvm/ADT/STLExtras.h"
  11. #include "llvm/ADT/SmallVector.h"
  12. #include "llvm/ADT/StringRef.h"
  13. #include "llvm/Support/raw_ostream.h"
  14. namespace Carbon {
  15. // An instance of a single error or warning. Information about the diagnostic
  16. // can be recorded into it for more complex consumers.
  17. //
  18. // TODO: turn this into a much more reasonable API when we add some actual
  19. // uses of it.
  20. struct Diagnostic {
  21. enum Level {
  22. // A warning diagnostic, indicating a likely problem with the program.
  23. Warning,
  24. // An error diagnostic, indicating that the program is not valid.
  25. Error,
  26. };
  27. struct Location {
  28. // Name of the file or buffer that this diagnostic refers to.
  29. std::string file_name;
  30. // 1-based line number.
  31. int32_t line_number;
  32. // 1-based column number.
  33. int32_t column_number;
  34. };
  35. Level level;
  36. Location location;
  37. llvm::StringRef short_name;
  38. std::string message;
  39. };
  40. // Receives diagnostics as they are emitted.
  41. class DiagnosticConsumer {
  42. public:
  43. virtual ~DiagnosticConsumer() = default;
  44. // Handle a diagnostic.
  45. virtual auto HandleDiagnostic(const Diagnostic& diagnostic) -> void = 0;
  46. // Flushes any buffered input.
  47. virtual auto Flush() -> void {}
  48. };
  49. // An interface that can translate some representation of a location into a
  50. // diagnostic location.
  51. //
  52. // TODO: Revisit this once the diagnostics machinery is more complete and see
  53. // if we can turn it into a `std::function`.
  54. template <typename LocationT>
  55. class DiagnosticLocationTranslator {
  56. public:
  57. virtual ~DiagnosticLocationTranslator() = default;
  58. [[nodiscard]] virtual auto GetLocation(LocationT loc)
  59. -> Diagnostic::Location = 0;
  60. };
  61. // CRTP base class for diagnostics. `DiagnosticEmitter` requires `ShortName` and
  62. // `Format`; `Message` is used by the default `Format` implementation. A simple
  63. // child will look like:
  64. //
  65. // struct MySimpleError : DiagnosticBase<MyError> {
  66. // static constexpr llvm::StringLiteral ShortName = "short-name";
  67. // static constexpr llvm::StringLiteral Message = "A message.";
  68. // };
  69. //
  70. // emitter.EmitError<MySimpleError>(location);
  71. //
  72. // A complex child may provide an alternate `Format` implementation:
  73. //
  74. // struct MyComplexError : DiagnosticBase<MyComplexError> {
  75. // static constexpr llvm::StringLiteral ShortName = "short-name";
  76. //
  77. // auto Format() -> std::string { return llvm::formatv("See {0}.", ref); }
  78. //
  79. // std::string ref;
  80. // };
  81. //
  82. // emitter.EmitError<MyComplexError>(location, {.ref = "ref"; });
  83. template <typename Derived>
  84. struct DiagnosticBase {
  85. static auto Format() -> std::string { return Derived::Message.str(); }
  86. };
  87. // Manages the creation of reports, the testing if diagnostics are enabled, and
  88. // the collection of reports.
  89. //
  90. // This class is parameterized by a location type, allowing different
  91. // diagnostic clients to provide location information in whatever form is most
  92. // convenient for them, such as a position within a buffer when lexing, a token
  93. // when parsing, or a parse tree node when type-checking, and to allow unit
  94. // tests to be decoupled from any concrete location representation.
  95. template <typename LocationT>
  96. class DiagnosticEmitter {
  97. public:
  98. // The `translator` and `consumer` are required to outlive the diagnostic
  99. // emitter.
  100. explicit DiagnosticEmitter(
  101. DiagnosticLocationTranslator<LocationT>& translator,
  102. DiagnosticConsumer& consumer)
  103. : translator_(&translator), consumer_(&consumer) {}
  104. ~DiagnosticEmitter() = default;
  105. // Emits an error unconditionally.
  106. template <typename DiagnosticT,
  107. typename = std::enable_if_t<
  108. std::is_base_of_v<DiagnosticBase<DiagnosticT>, DiagnosticT>>>
  109. auto EmitError(LocationT location, DiagnosticT diag) -> void {
  110. // TODO: Encode the diagnostic kind in the Diagnostic object rather than
  111. // hardcoding an "error: " prefix.
  112. consumer_->HandleDiagnostic({.level = Diagnostic::Error,
  113. .location = translator_->GetLocation(location),
  114. .short_name = DiagnosticT::ShortName,
  115. .message = diag.Format()});
  116. }
  117. // Emits a stateless error unconditionally.
  118. template <typename DiagnosticT>
  119. auto EmitError(LocationT location)
  120. -> std::enable_if_t<std::is_empty_v<DiagnosticT>> {
  121. EmitError<DiagnosticT>(location, {});
  122. }
  123. // Emits a warning if `F` returns true. `F` may or may not be called if the
  124. // warning is disabled.
  125. template <typename DiagnosticT>
  126. auto EmitWarningIf(LocationT location,
  127. llvm::function_ref<bool(DiagnosticT&)> f) -> void {
  128. // TODO(kfm): check if this warning is enabled at `location`.
  129. DiagnosticT diag;
  130. if (f(diag)) {
  131. // TODO: Encode the diagnostic kind in the Diagnostic object rather than
  132. // hardcoding a "warning: " prefix.
  133. consumer_->HandleDiagnostic(
  134. {.level = Diagnostic::Warning,
  135. .location = translator_->GetLocation(location),
  136. .short_name = DiagnosticT::ShortName,
  137. .message = diag.Format()});
  138. }
  139. }
  140. private:
  141. DiagnosticLocationTranslator<LocationT>* translator_;
  142. DiagnosticConsumer* consumer_;
  143. };
  144. inline auto ConsoleDiagnosticConsumer() -> DiagnosticConsumer& {
  145. struct Consumer : DiagnosticConsumer {
  146. auto HandleDiagnostic(const Diagnostic& d) -> void override {
  147. if (!d.location.file_name.empty()) {
  148. llvm::errs() << d.location.file_name << ":" << d.location.line_number
  149. << ":" << d.location.column_number << ": ";
  150. }
  151. llvm::errs() << d.message << "\n";
  152. }
  153. };
  154. static auto* consumer = new Consumer;
  155. return *consumer;
  156. }
  157. // Diagnostic consumer adaptor that tracks whether any errors have been
  158. // produced.
  159. class ErrorTrackingDiagnosticConsumer : public DiagnosticConsumer {
  160. public:
  161. explicit ErrorTrackingDiagnosticConsumer(DiagnosticConsumer& next_consumer)
  162. : next_consumer_(&next_consumer) {}
  163. auto HandleDiagnostic(const Diagnostic& diagnostic) -> void override {
  164. seen_error_ |= diagnostic.level == Diagnostic::Error;
  165. next_consumer_->HandleDiagnostic(diagnostic);
  166. }
  167. // Reset whether we've seen an error.
  168. auto Reset() -> void { seen_error_ = false; }
  169. // Returns whether we've seen an error since the last reset.
  170. auto seen_error() const -> bool { return seen_error_; }
  171. private:
  172. DiagnosticConsumer* next_consumer_;
  173. bool seen_error_ = false;
  174. };
  175. } // namespace Carbon
  176. #endif // TOOLCHAIN_DIAGNOSTICS_DIAGNOSTICEMITTER_H_