diagnostic_emitter.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
  5. #define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
  6. #include <functional>
  7. #include <string>
  8. #include <type_traits>
  9. #include <utility>
  10. #include "common/check.h"
  11. #include "llvm/ADT/Any.h"
  12. #include "llvm/ADT/SmallVector.h"
  13. #include "llvm/ADT/StringRef.h"
  14. #include "llvm/Support/FormatVariadic.h"
  15. #include "llvm/Support/raw_ostream.h"
  16. #include "toolchain/diagnostics/diagnostic_kind.h"
  17. namespace Carbon {
  18. enum class DiagnosticLevel : int8_t {
  19. // A note, not indicating an error on its own, but possibly providing context
  20. // for an error.
  21. Note,
  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. // Provides a definition of a diagnostic. For example:
  28. // CARBON_DIAGNOSTIC(MyDiagnostic, Error, "Invalid code!");
  29. // CARBON_DIAGNOSTIC(MyDiagnostic, Warning, "Found {0}, expected {1}.",
  30. // llvm::StringRef, llvm::StringRef);
  31. //
  32. // Arguments are passed to llvm::formatv; see:
  33. // https://llvm.org/doxygen/FormatVariadic_8h_source.html
  34. //
  35. // See `DiagnosticEmitter::Emit` for comments about argument lifetimes.
  36. #define CARBON_DIAGNOSTIC(DiagnosticName, Level, Format, ...) \
  37. static constexpr auto DiagnosticName = \
  38. ::Carbon::Internal::DiagnosticBase<__VA_ARGS__>( \
  39. ::Carbon::DiagnosticKind::DiagnosticName, \
  40. ::Carbon::DiagnosticLevel::Level, Format)
  41. // A location for a diagnostic in a file. The lifetime of a DiagnosticLocation
  42. // is required to be less than SourceBuffer that it refers to due to the
  43. // contained file_name and line references.
  44. struct DiagnosticLocation {
  45. // Name of the file or buffer that this diagnostic refers to.
  46. llvm::StringRef file_name;
  47. // A reference to the line of the error.
  48. llvm::StringRef line;
  49. // 1-based line number.
  50. int32_t line_number = -1;
  51. // 1-based column number.
  52. int32_t column_number = -1;
  53. };
  54. // A message composing a diagnostic. This may be the main message, but can also
  55. // be notes providing more information.
  56. struct DiagnosticMessage {
  57. explicit DiagnosticMessage(
  58. DiagnosticKind kind, DiagnosticLocation location,
  59. llvm::StringLiteral format, llvm::SmallVector<llvm::Any> format_args,
  60. std::function<std::string(const DiagnosticMessage&)> format_fn)
  61. : kind(kind),
  62. location(location),
  63. format(format),
  64. format_args(std::move(format_args)),
  65. format_fn(std::move(format_fn)) {}
  66. // The diagnostic's kind.
  67. DiagnosticKind kind;
  68. // The calculated location of the diagnostic.
  69. DiagnosticLocation location;
  70. // The diagnostic's format string. This, along with format_args, will be
  71. // passed to format_fn.
  72. llvm::StringLiteral format;
  73. // A list of format arguments.
  74. //
  75. // These may be used by non-standard consumers to inspect diagnostic details
  76. // without needing to parse the formatted string; however, it should be
  77. // understood that diagnostic formats are subject to change and the llvm::Any
  78. // offers limited compile-time type safety. Integration tests are required.
  79. llvm::SmallVector<llvm::Any> format_args;
  80. // Returns the formatted string. By default, this uses llvm::formatv.
  81. std::function<std::string(const DiagnosticMessage&)> format_fn;
  82. };
  83. // An instance of a single error or warning. Information about the diagnostic
  84. // can be recorded into it for more complex consumers.
  85. struct Diagnostic {
  86. // The diagnostic's level.
  87. DiagnosticLevel level;
  88. // The main error or warning.
  89. DiagnosticMessage message;
  90. // Notes that add context or supplemental information to the diagnostic.
  91. llvm::SmallVector<DiagnosticMessage> notes;
  92. };
  93. // Receives diagnostics as they are emitted.
  94. class DiagnosticConsumer {
  95. public:
  96. virtual ~DiagnosticConsumer() = default;
  97. // Handle a diagnostic.
  98. //
  99. // This relies on moves of the Diagnostic. At present, diagnostics are
  100. // allocated on the stack, so their lifetime is that of HandleDiagnostic.
  101. // However, SortingDiagnosticConsumer needs a longer lifetime, until all
  102. // diagnostics have been produced. As a consequence, it needs to either copy
  103. // or move the Diagnostic, and right now we're moving due to the overhead of
  104. // notes.
  105. //
  106. // At present, there is no persistent storage of diagnostics because IDEs
  107. // would be fine with diagnostics being printed immediately and discarded,
  108. // without SortingDiagnosticConsumer. If this becomes a performance issue, we
  109. // may want to investigate alternative ownership models that address both IDE
  110. // and CLI user needs.
  111. virtual auto HandleDiagnostic(Diagnostic diagnostic) -> void = 0;
  112. // Flushes any buffered input.
  113. virtual auto Flush() -> void {}
  114. };
  115. // An interface that can translate some representation of a location into a
  116. // diagnostic location.
  117. //
  118. // TODO: Revisit this once the diagnostics machinery is more complete and see
  119. // if we can turn it into a `std::function`.
  120. template <typename LocationT>
  121. class DiagnosticLocationTranslator {
  122. public:
  123. virtual ~DiagnosticLocationTranslator() = default;
  124. [[nodiscard]] virtual auto GetLocation(LocationT loc)
  125. -> DiagnosticLocation = 0;
  126. };
  127. namespace Internal {
  128. // Use the DIAGNOSTIC macro to instantiate this.
  129. // This stores static information about a diagnostic category.
  130. template <typename... Args>
  131. struct DiagnosticBase {
  132. explicit constexpr DiagnosticBase(DiagnosticKind kind, DiagnosticLevel level,
  133. llvm::StringLiteral format)
  134. : Kind(kind), Level(level), Format(format) {}
  135. // Calls formatv with the diagnostic's arguments.
  136. auto FormatFn(const DiagnosticMessage& message) const -> std::string {
  137. return FormatFnImpl(message, std::make_index_sequence<sizeof...(Args)>());
  138. };
  139. // The diagnostic's kind.
  140. DiagnosticKind Kind;
  141. // The diagnostic's level.
  142. DiagnosticLevel Level;
  143. // The diagnostic's format for llvm::formatv.
  144. llvm::StringLiteral Format;
  145. private:
  146. // Handles the cast of llvm::Any to Args types for formatv.
  147. // TODO: Custom formatting can be provided with an format_provider, but that
  148. // affects all formatv calls. Consider replacing formatv with a custom call
  149. // that allows diagnostic-specific formatting.
  150. template <std::size_t... N>
  151. inline auto FormatFnImpl(const DiagnosticMessage& message,
  152. std::index_sequence<N...> /*indices*/) const
  153. -> std::string {
  154. assert(message.format_args.size() == sizeof...(Args));
  155. return llvm::formatv(message.format.data(),
  156. llvm::any_cast<Args>(message.format_args[N])...);
  157. }
  158. };
  159. // Disable type deduction based on `args`; the type of `diagnostic_base`
  160. // determines the diagnostic's parameter types.
  161. template <typename Arg>
  162. using NoTypeDeduction = std::common_type_t<Arg>;
  163. } // namespace Internal
  164. // Manages the creation of reports, the testing if diagnostics are enabled, and
  165. // the collection of reports.
  166. //
  167. // This class is parameterized by a location type, allowing different
  168. // diagnostic clients to provide location information in whatever form is most
  169. // convenient for them, such as a position within a buffer when lexing, a token
  170. // when parsing, or a parse tree node when type-checking, and to allow unit
  171. // tests to be decoupled from any concrete location representation.
  172. template <typename LocationT>
  173. class DiagnosticEmitter {
  174. public:
  175. // A builder-pattern type to provide a fluent interface for constructing
  176. // a more complex diagnostic. See `DiagnosticEmitter::Build` for the
  177. // expected usage.
  178. class DiagnosticBuilder {
  179. public:
  180. // Adds a note diagnostic attached to the main diagnostic being built.
  181. // The API mirrors the main emission API: `DiagnosticEmitter::Emit`.
  182. // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
  183. template <typename... Args>
  184. auto Note(LocationT location,
  185. const Internal::DiagnosticBase<Args...>& diagnostic_base,
  186. Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
  187. CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note)
  188. << static_cast<int>(diagnostic_base.Level);
  189. diagnostic_.notes.push_back(MakeMessage(
  190. emitter_, location, diagnostic_base, {llvm::Any(args)...}));
  191. return *this;
  192. }
  193. // Emits the built diagnostic and its attached notes.
  194. // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
  195. template <typename... Args>
  196. auto Emit() -> void {
  197. emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
  198. }
  199. private:
  200. friend class DiagnosticEmitter<LocationT>;
  201. template <typename... Args>
  202. explicit DiagnosticBuilder(
  203. DiagnosticEmitter<LocationT>* emitter, LocationT location,
  204. const Internal::DiagnosticBase<Args...>& diagnostic_base,
  205. llvm::SmallVector<llvm::Any> args)
  206. : emitter_(emitter),
  207. diagnostic_(
  208. {.level = diagnostic_base.Level,
  209. .message = MakeMessage(emitter, location, diagnostic_base,
  210. std::move(args))}) {
  211. CARBON_CHECK(diagnostic_base.Level != DiagnosticLevel::Note);
  212. }
  213. template <typename... Args>
  214. static auto MakeMessage(
  215. DiagnosticEmitter<LocationT>* emitter, LocationT location,
  216. const Internal::DiagnosticBase<Args...>& diagnostic_base,
  217. llvm::SmallVector<llvm::Any> args) -> DiagnosticMessage {
  218. return DiagnosticMessage(
  219. diagnostic_base.Kind, emitter->translator_->GetLocation(location),
  220. diagnostic_base.Format, std::move(args),
  221. [&diagnostic_base](const DiagnosticMessage& message) -> std::string {
  222. return diagnostic_base.FormatFn(message);
  223. });
  224. }
  225. DiagnosticEmitter<LocationT>* emitter_;
  226. Diagnostic diagnostic_;
  227. };
  228. // The `translator` and `consumer` are required to outlive the diagnostic
  229. // emitter.
  230. explicit DiagnosticEmitter(
  231. DiagnosticLocationTranslator<LocationT>& translator,
  232. DiagnosticConsumer& consumer)
  233. : translator_(&translator), consumer_(&consumer) {}
  234. ~DiagnosticEmitter() = default;
  235. // Emits an error.
  236. //
  237. // When passing arguments, they may be buffered. As a consequence, lifetimes
  238. // may outlive the `Emit` call.
  239. template <typename... Args>
  240. auto Emit(LocationT location,
  241. const Internal::DiagnosticBase<Args...>& diagnostic_base,
  242. Internal::NoTypeDeduction<Args>... args) -> void {
  243. DiagnosticBuilder(this, location, diagnostic_base, {llvm::Any(args)...})
  244. .Emit();
  245. }
  246. // A fluent interface for building a diagnostic and attaching notes for added
  247. // context or information. For example:
  248. //
  249. // emitter_.Build(location1, MyDiagnostic)
  250. // .Note(location2, MyDiagnosticNote)
  251. // .Emit();
  252. template <typename... Args>
  253. auto Build(LocationT location,
  254. const Internal::DiagnosticBase<Args...>& diagnostic_base,
  255. Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder {
  256. return DiagnosticBuilder(this, location, diagnostic_base,
  257. {llvm::Any(args)...});
  258. }
  259. private:
  260. DiagnosticLocationTranslator<LocationT>* translator_;
  261. DiagnosticConsumer* consumer_;
  262. };
  263. class StreamDiagnosticConsumer : public DiagnosticConsumer {
  264. public:
  265. explicit StreamDiagnosticConsumer(llvm::raw_ostream& stream)
  266. : stream_(&stream) {}
  267. auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
  268. Print(diagnostic.message);
  269. for (const auto& note : diagnostic.notes) {
  270. Print(note);
  271. }
  272. }
  273. auto Print(const DiagnosticMessage& message) -> void {
  274. *stream_ << message.location.file_name;
  275. if (message.location.line_number > 0) {
  276. *stream_ << ":" << message.location.line_number;
  277. if (message.location.column_number > 0) {
  278. *stream_ << ":" << message.location.column_number;
  279. }
  280. }
  281. *stream_ << ": " << message.format_fn(message) << "\n";
  282. if (message.location.column_number > 0) {
  283. *stream_ << message.location.line << "\n";
  284. stream_->indent(message.location.column_number - 1);
  285. *stream_ << "^\n";
  286. }
  287. }
  288. private:
  289. llvm::raw_ostream* stream_;
  290. };
  291. inline auto ConsoleDiagnosticConsumer() -> DiagnosticConsumer& {
  292. static auto* consumer = new StreamDiagnosticConsumer(llvm::errs());
  293. return *consumer;
  294. }
  295. // Diagnostic consumer adaptor that tracks whether any errors have been
  296. // produced.
  297. class ErrorTrackingDiagnosticConsumer : public DiagnosticConsumer {
  298. public:
  299. explicit ErrorTrackingDiagnosticConsumer(DiagnosticConsumer& next_consumer)
  300. : next_consumer_(&next_consumer) {}
  301. auto HandleDiagnostic(Diagnostic diagnostic) -> void override {
  302. seen_error_ |= diagnostic.level == DiagnosticLevel::Error;
  303. next_consumer_->HandleDiagnostic(std::move(diagnostic));
  304. }
  305. // Reset whether we've seen an error.
  306. auto Reset() -> void { seen_error_ = false; }
  307. // Returns whether we've seen an error since the last reset.
  308. auto seen_error() const -> bool { return seen_error_; }
  309. private:
  310. DiagnosticConsumer* next_consumer_;
  311. bool seen_error_ = false;
  312. };
  313. } // namespace Carbon
  314. #endif // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_