| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- // 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_EMITTER_H_
- #define CARBON_TOOLCHAIN_DIAGNOSTICS_EMITTER_H_
- #include <cstdint>
- #include <string>
- #include <type_traits>
- #include <utility>
- #include "common/check.h"
- #include "llvm/ADT/Any.h"
- #include "llvm/ADT/SmallVector.h"
- #include "llvm/Support/FormatVariadic.h"
- #include "toolchain/diagnostics/consumer.h"
- #include "toolchain/diagnostics/diagnostic.h"
- #include "toolchain/diagnostics/kind.h"
- namespace Carbon::Diagnostics {
- namespace Internal {
- // Disable type deduction based on `args`; the type of `diagnostic_base`
- // determines the diagnostic's parameter types.
- template <typename Arg>
- using NoTypeDeduction = std::type_identity_t<Arg>;
- } // namespace Internal
- template <typename LocT, typename AnnotateFn>
- class AnnotationScope;
- // The result of `DiagnosticConvert::ConvertLoc`. This is non-templated to allow
- // sharing across converters.
- struct ConvertedLoc {
- // Becomes Message::loc.
- Loc loc;
- // Becomes Diagnostic::last_byte_offset.
- int32_t last_byte_offset;
- };
- // Used by types to indicate a diagnostic type conversion that results in the
- // provided StorageType. For example, to convert NameId to a std::string, we
- // write:
- //
- // struct NameId {
- // using DiagnosticType = Diagnostics::TypeInfo<std::string>;
- // };
- template <typename StorageTypeT>
- struct TypeInfo {
- using StorageType = StorageTypeT;
- };
- // Manages the creation of reports, the testing if diagnostics are enabled, and
- // the collection of reports.
- //
- // This class is parameterized by a location type, allowing different
- // diagnostic clients to provide location information in whatever form is most
- // convenient for them, such as a position within a buffer when lexing, a token
- // when parsing, or a parse tree node when type-checking, and to allow unit
- // tests to be decoupled from any concrete location representation.
- template <typename LocT>
- class Emitter {
- public:
- // A builder-pattern type to provide a fluent interface for constructing
- // a more complex diagnostic. See `Emitter::Build` for the
- // expected usage.
- // This is nodiscard to protect against accidentally building a diagnostic
- // without emitting it.
- class [[nodiscard]] Builder {
- public:
- // Builder is move-only and cannot be copied.
- Builder(Builder&&) noexcept = default;
- auto operator=(Builder&&) noexcept -> Builder& = default;
- // Overrides the snippet for the most recently added diagnostic or note with
- // the given text. The provided override should include the caret text as
- // well as the source snippet. An empty snippet restores the default
- // behavior of printing the original source line.
- auto OverrideSnippet(llvm::StringRef snippet) -> Builder&;
- // Adds a Note about the diagnostic, attached to the main diagnostic being
- // built. The API mirrors the main emission API: `Emitter::Emit`. For the
- // expected usage see the builder API: `Emitter::Build`.
- template <typename... Args>
- auto Note(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> Builder&;
- // Emits the built diagnostic and its attached notes.
- // For the expected usage see the builder API: `Emitter::Build`.
- template <typename... Args>
- auto Emit() & -> void;
- // Prevent trivial uses of the builder; always `static_assert`s.
- template <typename... Args>
- auto Emit() && -> void;
- // Returns true if this Builder may emit a diagnostic. Can be used
- // to avoid excess work computing notes, etc, if no diagnostic is going to
- // be emitted anyway.
- explicit operator bool() { return emitter_; }
- private:
- friend class Emitter<LocT>;
- friend class ContextBuilder;
- template <typename... Args>
- explicit Builder(Emitter<LocT>* emitter, LocT loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args);
- // Adds a message to the diagnostic, handling conversion of the location and
- // arguments.
- template <typename... Args>
- auto AddMessage(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args) -> void;
- // Adds a message to the diagnostic, handling conversion of the arguments. A
- // Loc must be provided instead of a LocT in order to
- // avoid potential recursion.
- template <typename... Args>
- auto AddMessageWithLoc(Loc loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args) -> void;
- // 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, size_t... N>
- static auto FormatFn(const Message& message,
- std::index_sequence<N...> /*indices*/) -> std::string;
- // Whether a Context or SoftContext message has been added to the Builder.
- auto has_context_message() const -> bool { return has_context_message_; }
- Emitter<LocT>* emitter_;
- Diagnostic diagnostic_;
- bool has_context_message_ = false;
- };
- class ContextBuilder {
- public:
- // Adds a Context describing a higher level operation that failed due to the
- // diagnostic being built. The API mirrors the main emission API:
- // `Emitter::Emit`. For the expected usage see the builder API:
- // `Emitter::Build`.
- template <typename... Args>
- auto Context(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> ContextBuilder&;
- private:
- friend class Emitter<LocT>;
- explicit ContextBuilder(Emitter<LocT>* emitter, Builder* builder)
- : emitter_(emitter), builder_(builder) {}
- Emitter<LocT>* emitter_;
- Builder* builder_;
- };
- // `consumer` is required to outlive the diagnostic emitter.
- explicit Emitter(Consumer* consumer) : consumer_(consumer) {}
- virtual ~Emitter() = default;
- // Emits an error.
- //
- // When passing arguments, they may be buffered. As a consequence, lifetimes
- // may outlive the `Emit` call.
- template <typename... Args>
- auto Emit(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> void;
- // A fluent interface for building a diagnostic and attaching notes for added
- // context or information. For example:
- //
- // emitter_.Build(loc1, MyDiagnostic)
- // .Note(loc2, MyDiagnosticNote)
- // .Emit();
- template <typename... Args>
- auto Build(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> Builder;
- // Adds a flush function to flush pending diagnostics that might be enqueued
- // and not yet emitted. The flush function will be called whenever `Flush` is
- // called.
- //
- // No mechanism is provided to unregister a flush function, so the function
- // must ensure that it remains callable until the emitter is destroyed.
- //
- // This is used to register a handler to flush diagnostics from Clang.
- auto AddFlushFn(std::function<auto()->void> flush_fn) -> void {
- flush_fns_.push_back(std::move(flush_fn));
- }
- // Flush all pending diagnostics that are queued externally, such as Clang
- // diagnostics. This should not be called when the external source might be in
- // the middle of producing a diagnostic, such as between Clang producing an
- // error and producing the attached notes.
- //
- // This is called automatically before any diagnostic annotator is added or
- // removed, to flush any pending diagnostics with suitable notes attached, and
- // when the emitter is destroyed.
- auto Flush() -> void {
- for (auto& flush_fn : flush_fns_) {
- flush_fn();
- }
- }
- // Verifies that a callback is registered to provide context if a diagnostic
- // is emitted. Allows a code path to require context, which then means its
- // diagnostics to be framed as Notes.
- //
- // This is best effort as the registered callback can in practice do nothing,
- // but that would be highly unusual.
- auto CheckHasContext() -> void { CARBON_CHECK(!context_fns_.empty()); }
- protected:
- // Callback type used to report context messages from ConvertLoc.
- // Note that the first parameter type is Loc rather than
- // LocT, because ConvertLoc must not recurse.
- using ContextFnT =
- llvm::function_ref<auto(Loc, const DiagnosticBase<>&)->void>;
- // Converts a LocT to a Loc and its `last_byte_offset` (see
- // `Message`). ConvertLoc may invoke context_fn to provide context
- // messages.
- virtual auto ConvertLoc(LocT loc, ContextFnT context_fn) const
- -> ConvertedLoc = 0;
- // Converts arg types as needed. Most children don't customize conversion, so
- // the default returns the argument unchanged.
- virtual auto ConvertArg(llvm::Any arg) const -> llvm::Any { return arg; }
- private:
- // Converts an argument to llvm::Any for storage, handling input to storage
- // type conversion when needed.
- template <typename Arg>
- auto MakeAny(Arg arg) -> llvm::Any;
- template <typename OtherLocT, typename ContextFn>
- friend class ContextScope;
- template <typename OtherLocT, typename ContextFn>
- friend class AnnotationScope;
- friend class NoLocEmitter;
- Consumer* consumer_;
- llvm::SmallVector<std::function<auto()->void>, 1> flush_fns_;
- llvm::SmallVector<llvm::function_ref<auto(ContextBuilder& builder)->void>>
- context_fns_;
- llvm::SmallVector<llvm::function_ref<auto(Builder& builder)->void>>
- annotate_fns_;
- };
- // This relies on `void*` location handling on `Emitter`.
- //
- // TODO: Based on how this ends up used or if we get more distinct emitters, it
- // might be worth considering having diagnostics specify that they don't apply
- // to source-location carrying emitters. For example, this might look like a
- // `CARBON_NO_LOC_DIAGNOSTIC` macro, or some other factoring. But it might end
- // up being more noise than it is worth.
- class NoLocEmitter : public Emitter<void*> {
- public:
- using Emitter::Emitter;
- template <typename LocT>
- explicit NoLocEmitter(const Emitter<LocT>& emitter)
- : Emitter(emitter.consumer_) {}
- // Emits an error. This specialization only applies to
- // `NoLocEmitter`.
- template <typename... Args>
- auto Emit(const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> void {
- Emitter::Emit(nullptr, diagnostic_base, args...);
- }
- protected:
- auto ConvertLoc(void* /*loc*/, ContextFnT /*context_fn*/) const
- -> ConvertedLoc override {
- return {.loc = {.filename = ""}, .last_byte_offset = -1};
- }
- };
- // An RAII object that denotes a scope in which any diagnostic produced should
- // become a note attached to the higher-level operation failure described by a
- // Context message.
- //
- // This object is given a function `context` that will be called with a
- // `ContextBuilder& builder` for any diagnostic that is emitted through the
- // given emitter. That function can provide a context message that explains the
- // higher level failure caused by the diagnostic by calling `builder.Context`.
- template <typename LocT, typename ContextFn>
- class ContextScope {
- public:
- ContextScope(Emitter<LocT>* emitter, ContextFn context)
- requires requires(ContextFn context,
- Emitter<LocT>::ContextBuilder& builder) {
- { context(builder) } -> std::same_as<void>;
- }
- : emitter_(emitter), context_(std::move(context)) {
- emitter_->Flush();
- emitter_->context_fns_.push_back(context_);
- }
- ~ContextScope() {
- emitter_->Flush();
- emitter_->context_fns_.pop_back();
- }
- private:
- Emitter<LocT>* emitter_;
- // Make a copy of the context function to ensure that it lives long enough.
- ContextFn context_;
- };
- template <typename LocT, typename ContextFn>
- ContextScope(Emitter<LocT>* emitter, ContextFn context)
- -> ContextScope<LocT, ContextFn>;
- // An RAII object that denotes a scope in which any diagnostic produced should
- // be annotated in some way.
- //
- // This object is given a function `annotate` that will be called with a
- // `Builder& builder` for any diagnostic that is emitted through the
- // given emitter. That function can annotate the diagnostic by calling
- // `builder.Note` to add notes.
- template <typename LocT, typename AnnotateFn>
- class AnnotationScope {
- public:
- AnnotationScope(Emitter<LocT>* emitter, AnnotateFn annotate)
- requires requires(AnnotateFn annotate, Emitter<LocT>::Builder& builder) {
- { annotate(builder) } -> std::same_as<void>;
- }
- : emitter_(emitter), annotate_(std::move(annotate)) {
- emitter_->Flush();
- emitter_->annotate_fns_.push_back(annotate_);
- }
- ~AnnotationScope() {
- emitter_->Flush();
- emitter_->annotate_fns_.pop_back();
- }
- private:
- Emitter<LocT>* emitter_;
- // Make a copy of the annotation function to ensure that it lives long enough.
- AnnotateFn annotate_;
- };
- template <typename LocT, typename AnnotateFn>
- AnnotationScope(Emitter<LocT>* emitter, AnnotateFn annotate)
- -> AnnotationScope<LocT, AnnotateFn>;
- // ============================================================================
- // Only internal implementation details below this point.
- // ============================================================================
- namespace Internal {
- // Determines whether there's a DiagnosticType member on Arg.
- // Used by Emitter.
- template <typename Arg>
- concept HasDiagnosticType = requires { typename Arg::DiagnosticType; };
- // The default implementation with no conversion.
- template <typename Arg>
- struct DiagnosticTypeForArg : public TypeInfo<Arg> {};
- // Exposes a custom conversion for an argument type.
- template <typename Arg>
- requires HasDiagnosticType<Arg>
- struct DiagnosticTypeForArg<Arg> : public Arg::DiagnosticType {};
- } // namespace Internal
- template <typename LocT>
- auto Emitter<LocT>::Builder::OverrideSnippet(llvm::StringRef snippet)
- -> Builder& {
- diagnostic_.messages.back().loc.snippet = snippet;
- return *this;
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Builder::Note(
- LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> Builder& {
- CARBON_CHECK(diagnostic_base.Level == Level::Note ||
- diagnostic_base.Level == Level::LocationInfo,
- "{0}", static_cast<int>(diagnostic_base.Level));
- AddMessage(LocT(loc), diagnostic_base, {emitter_->MakeAny<Args>(args)...});
- return *this;
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Builder::Emit() & -> void {
- for (auto annotate_fn : llvm::reverse(emitter_->annotate_fns_)) {
- annotate_fn(*this);
- }
- emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Builder::Emit() && -> void {
- static_assert(false,
- "Use `emitter.Emit(...)` or "
- "`emitter.Build(...).Note(...).Emit(...)` "
- "instead of `emitter.Build(...).Emit(...)`");
- }
- template <typename LocT>
- template <typename... Args>
- Emitter<LocT>::Builder::Builder(Emitter<LocT>* emitter, LocT loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args)
- : emitter_(emitter),
- diagnostic_({.level = diagnostic_base.Level,
- .is_on_scope = diagnostic_base.IsOnScope}) {
- CARBON_CHECK(diagnostic_.level >= Level::Warning,
- "building diagnostic with level {0}; expected Warning or Error",
- diagnostic_.level);
- ContextBuilder context_builder(emitter, this);
- for (auto context_fn : emitter_->context_fns_) {
- context_fn(context_builder);
- }
- AddMessage(LocT(loc), diagnostic_base, std::move(args));
- CARBON_CHECK(diagnostic_base.Level != Level::Note);
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Builder::AddMessage(
- LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args) -> void {
- auto converted = emitter_->ConvertLoc(
- loc,
- [&](Loc context_loc, const DiagnosticBase<>& context_diagnostic_base) {
- AddMessageWithLoc(context_loc, context_diagnostic_base, {});
- });
- // Use the last byte offset from the first message.
- if (diagnostic_.messages.empty()) {
- diagnostic_.last_byte_offset = converted.last_byte_offset;
- }
- AddMessageWithLoc(converted.loc, diagnostic_base, args);
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Builder::AddMessageWithLoc(
- Loc loc, const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args) -> void {
- CARBON_CHECK(
- diagnostic_base.Level <= diagnostic_.level,
- "message with level {0} is higher than the diagnostic's level {1}",
- diagnostic_base.Level, diagnostic_.level);
- if (diagnostic_base.Level == Level::SoftContext ||
- diagnostic_base.Level == Level::Context) {
- has_context_message_ = true;
- }
- diagnostic_.messages.push_back(
- Message{.kind = diagnostic_base.Kind,
- .level = diagnostic_base.Level,
- .loc = loc,
- .format = diagnostic_base.Format,
- .format_args = std::move(args),
- .format_fn = [](const Message& message) -> std::string {
- return FormatFn<Args...>(
- message, std::make_index_sequence<sizeof...(Args)>());
- }});
- }
- template <typename LocT>
- template <typename... Args, size_t... N>
- auto Emitter<LocT>::Builder::FormatFn(const Message& 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),
- "Argument count mismatch on {0}: {1} != {2}", message.kind,
- message.format_args.size(), sizeof...(Args));
- return llvm::formatv(
- message.format.data(),
- llvm::any_cast<
- typename Internal::DiagnosticTypeForArg<Args>::StorageType>(
- message.format_args[N])...);
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Emit(LocT loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> void {
- Builder builder(this, loc, diagnostic_base, {MakeAny<Args>(args)...});
- builder.Emit();
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::ContextBuilder::Context(
- LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> ContextBuilder& {
- CARBON_CHECK(diagnostic_base.Level == Level::SoftContext ||
- diagnostic_base.Level == Level::Context,
- "{0}", static_cast<int>(diagnostic_base.Level));
- if (builder_->has_context_message() &&
- diagnostic_base.Level == Level::SoftContext) {
- return *this;
- }
- builder_->AddMessage(LocT(loc), diagnostic_base,
- {emitter_->template MakeAny<Args>(args)...});
- return *this;
- }
- template <typename LocT>
- template <typename... Args>
- auto Emitter<LocT>::Build(LocT loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> Builder {
- return Builder(this, loc, diagnostic_base, {MakeAny<Args>(args)...});
- }
- template <typename LocT>
- template <typename Arg>
- auto Emitter<LocT>::MakeAny(Arg arg) -> llvm::Any {
- llvm::Any converted = ConvertArg(arg);
- using Storage = Internal::DiagnosticTypeForArg<Arg>::StorageType;
- CARBON_CHECK(llvm::any_cast<Storage>(&converted),
- "Failed to convert argument of type {0} to its storage type {1}",
- typeid(Arg).name(), typeid(Storage).name());
- return converted;
- }
- } // namespace Carbon::Diagnostics
- #endif // CARBON_TOOLCHAIN_DIAGNOSTICS_EMITTER_H_
|