diagnostic_emitter.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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 <cstdint>
  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/Support/FormatVariadic.h"
  14. #include "toolchain/diagnostics/diagnostic.h"
  15. #include "toolchain/diagnostics/diagnostic_consumer.h"
  16. #include "toolchain/diagnostics/diagnostic_converter.h"
  17. #include "toolchain/diagnostics/diagnostic_kind.h"
  18. namespace Carbon {
  19. namespace Internal {
  20. // Disable type deduction based on `args`; the type of `diagnostic_base`
  21. // determines the diagnostic's parameter types.
  22. template <typename Arg>
  23. using NoTypeDeduction = std::type_identity_t<Arg>;
  24. } // namespace Internal
  25. template <typename LocT, typename AnnotateFn>
  26. class DiagnosticAnnotationScope;
  27. // Manages the creation of reports, the testing if diagnostics are enabled, and
  28. // the collection of reports.
  29. //
  30. // This class is parameterized by a location type, allowing different
  31. // diagnostic clients to provide location information in whatever form is most
  32. // convenient for them, such as a position within a buffer when lexing, a token
  33. // when parsing, or a parse tree node when type-checking, and to allow unit
  34. // tests to be decoupled from any concrete location representation.
  35. template <typename LocT>
  36. class DiagnosticEmitter {
  37. public:
  38. // A builder-pattern type to provide a fluent interface for constructing
  39. // a more complex diagnostic. See `DiagnosticEmitter::Build` for the
  40. // expected usage.
  41. // This is nodiscard to protect against accidentally building a diagnostic
  42. // without emitting it.
  43. class [[nodiscard]] DiagnosticBuilder {
  44. public:
  45. // DiagnosticBuilder is move-only and cannot be copied.
  46. DiagnosticBuilder(DiagnosticBuilder&&) noexcept = default;
  47. auto operator=(DiagnosticBuilder&&) noexcept
  48. -> DiagnosticBuilder& = default;
  49. // Adds a note diagnostic attached to the main diagnostic being built.
  50. // The API mirrors the main emission API: `DiagnosticEmitter::Emit`.
  51. // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
  52. template <typename... Args>
  53. auto Note(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
  54. Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
  55. if (!emitter_) {
  56. return *this;
  57. }
  58. CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note ||
  59. diagnostic_base.Level == DiagnosticLevel::LocationInfo,
  60. "{0}", static_cast<int>(diagnostic_base.Level));
  61. AddMessage(loc, diagnostic_base, {emitter_->MakeAny<Args>(args)...});
  62. return *this;
  63. }
  64. // Emits the built diagnostic and its attached notes.
  65. // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
  66. template <typename... Args>
  67. auto Emit() -> void {
  68. if (!emitter_) {
  69. return;
  70. }
  71. for (auto annotate_fn : emitter_->annotate_fns_) {
  72. annotate_fn(*this);
  73. }
  74. emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
  75. }
  76. // Returns true if this DiagnosticBuilder may emit a diagnostic. Can be used
  77. // to avoid excess work computing notes, etc, if no diagnostic is going to
  78. // be emitted anyway.
  79. explicit operator bool() { return emitter_; }
  80. private:
  81. friend class DiagnosticEmitter<LocT>;
  82. template <typename... Args>
  83. explicit DiagnosticBuilder(DiagnosticEmitter<LocT>* emitter, LocT loc,
  84. const DiagnosticBase<Args...>& diagnostic_base,
  85. llvm::SmallVector<llvm::Any> args)
  86. : emitter_(emitter), diagnostic_({.level = diagnostic_base.Level}) {
  87. AddMessage(loc, diagnostic_base, std::move(args));
  88. CARBON_CHECK(diagnostic_base.Level != DiagnosticLevel::Note);
  89. }
  90. // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
  91. // be silently ignored.
  92. DiagnosticBuilder() : emitter_(nullptr) {}
  93. // Adds a message to the diagnostic, handling conversion of the location and
  94. // arguments.
  95. template <typename... Args>
  96. auto AddMessage(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
  97. llvm::SmallVector<llvm::Any> args) -> void {
  98. if (!emitter_) {
  99. return;
  100. }
  101. AddMessageWithDiagnosticLoc(
  102. emitter_->converter_->ConvertLoc(
  103. loc,
  104. [&](DiagnosticLoc context_loc,
  105. const DiagnosticBase<>& context_diagnostic_base) {
  106. AddMessageWithDiagnosticLoc(context_loc,
  107. context_diagnostic_base, {});
  108. }),
  109. diagnostic_base, args);
  110. }
  111. // Adds a message to the diagnostic, handling conversion of the arguments. A
  112. // DiagnosticLoc must be provided instead of a LocT in order to
  113. // avoid potential recursion.
  114. template <typename... Args>
  115. auto AddMessageWithDiagnosticLoc(
  116. DiagnosticLoc loc, const DiagnosticBase<Args...>& diagnostic_base,
  117. llvm::SmallVector<llvm::Any> args) -> void {
  118. if (!emitter_) {
  119. return;
  120. }
  121. diagnostic_.messages.emplace_back(DiagnosticMessage{
  122. .kind = diagnostic_base.Kind,
  123. .level = diagnostic_base.Level,
  124. .loc = loc,
  125. .format = diagnostic_base.Format,
  126. .format_args = std::move(args),
  127. .format_fn = [](const DiagnosticMessage& message) -> std::string {
  128. return FormatFn<Args...>(
  129. message, std::make_index_sequence<sizeof...(Args)>());
  130. }});
  131. }
  132. // Handles the cast of llvm::Any to Args types for formatv.
  133. // TODO: Custom formatting can be provided with an format_provider, but that
  134. // affects all formatv calls. Consider replacing formatv with a custom call
  135. // that allows diagnostic-specific formatting.
  136. template <typename... Args, size_t... N>
  137. static auto FormatFn(const DiagnosticMessage& message,
  138. std::index_sequence<N...> /*indices*/) -> std::string {
  139. static_assert(sizeof...(Args) == sizeof...(N), "Invalid template args");
  140. CARBON_CHECK(message.format_args.size() == sizeof...(Args),
  141. "Argument count mismatch on {0}: {1} != {2}", message.kind,
  142. message.format_args.size(), sizeof...(Args));
  143. return llvm::formatv(
  144. message.format.data(),
  145. llvm::any_cast<
  146. typename Internal::DiagnosticTypeForArg<Args>::StorageType>(
  147. message.format_args[N])...);
  148. }
  149. DiagnosticEmitter<LocT>* emitter_;
  150. Diagnostic diagnostic_;
  151. };
  152. // The `converter` and `consumer` are required to outlive the diagnostic
  153. // emitter.
  154. explicit DiagnosticEmitter(DiagnosticConverter<LocT>& converter,
  155. DiagnosticConsumer& consumer)
  156. : converter_(&converter), consumer_(&consumer) {}
  157. ~DiagnosticEmitter() = default;
  158. // Emits an error.
  159. //
  160. // When passing arguments, they may be buffered. As a consequence, lifetimes
  161. // may outlive the `Emit` call.
  162. template <typename... Args>
  163. auto Emit(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
  164. Internal::NoTypeDeduction<Args>... args) -> void {
  165. DiagnosticBuilder(this, loc, diagnostic_base, {MakeAny<Args>(args)...})
  166. .Emit();
  167. }
  168. // A fluent interface for building a diagnostic and attaching notes for added
  169. // context or information. For example:
  170. //
  171. // emitter_.Build(loc1, MyDiagnostic)
  172. // .Note(loc2, MyDiagnosticNote)
  173. // .Emit();
  174. template <typename... Args>
  175. auto Build(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
  176. Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder {
  177. return DiagnosticBuilder(this, loc, diagnostic_base,
  178. {MakeAny<Args>(args)...});
  179. }
  180. // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
  181. // be silently ignored.
  182. auto BuildSuppressed() -> DiagnosticBuilder { return DiagnosticBuilder(); }
  183. private:
  184. // Converts an argument to llvm::Any for storage, handling input to storage
  185. // type conversion when needed.
  186. template <typename Arg>
  187. auto MakeAny(Arg arg) -> llvm::Any {
  188. llvm::Any converted = converter_->ConvertArg(arg);
  189. using Storage = Internal::DiagnosticTypeForArg<Arg>::StorageType;
  190. CARBON_CHECK(
  191. llvm::any_cast<Storage>(&converted),
  192. "Failed to convert argument of type {0} to its storage type {1}",
  193. typeid(Arg).name(), typeid(Storage).name());
  194. return converted;
  195. }
  196. template <typename OtherLocT, typename AnnotateFn>
  197. friend class DiagnosticAnnotationScope;
  198. DiagnosticConverter<LocT>* converter_;
  199. DiagnosticConsumer* consumer_;
  200. llvm::SmallVector<llvm::function_ref<auto(DiagnosticBuilder& builder)->void>>
  201. annotate_fns_;
  202. };
  203. // An RAII object that denotes a scope in which any diagnostic produced should
  204. // be annotated in some way.
  205. //
  206. // This object is given a function `annotate` that will be called with a
  207. // `DiagnosticBuilder& builder` for any diagnostic that is emitted through the
  208. // given emitter. That function can annotate the diagnostic by calling
  209. // `builder.Note` to add notes.
  210. template <typename LocT, typename AnnotateFn>
  211. class DiagnosticAnnotationScope {
  212. public:
  213. DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter,
  214. AnnotateFn annotate)
  215. : emitter_(emitter), annotate_(std::move(annotate)) {
  216. emitter_->annotate_fns_.push_back(annotate_);
  217. }
  218. ~DiagnosticAnnotationScope() { emitter_->annotate_fns_.pop_back(); }
  219. private:
  220. DiagnosticEmitter<LocT>* emitter_;
  221. // Make a copy of the annotation function to ensure that it lives long enough.
  222. AnnotateFn annotate_;
  223. };
  224. template <typename LocT, typename AnnotateFn>
  225. DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter, AnnotateFn annotate)
  226. -> DiagnosticAnnotationScope<LocT, AnnotateFn>;
  227. } // namespace Carbon
  228. #endif // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_