diagnostic_emitter.h 17 KB

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