| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- // 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_DIAGNOSTIC_EMITTER_H_
- #define CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_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/diagnostic.h"
- #include "toolchain/diagnostics/diagnostic_consumer.h"
- #include "toolchain/diagnostics/diagnostic_converter.h"
- #include "toolchain/diagnostics/diagnostic_kind.h"
- namespace Carbon {
- 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 DiagnosticAnnotationScope;
- // 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 DiagnosticEmitter {
- public:
- // A builder-pattern type to provide a fluent interface for constructing
- // a more complex diagnostic. See `DiagnosticEmitter::Build` for the
- // expected usage.
- // This is nodiscard to protect against accidentally building a diagnostic
- // without emitting it.
- class [[nodiscard]] DiagnosticBuilder {
- public:
- // DiagnosticBuilder is move-only and cannot be copied.
- DiagnosticBuilder(DiagnosticBuilder&&) noexcept = default;
- auto operator=(DiagnosticBuilder&&) noexcept
- -> DiagnosticBuilder& = default;
- // Adds a note diagnostic attached to the main diagnostic being built.
- // The API mirrors the main emission API: `DiagnosticEmitter::Emit`.
- // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
- template <typename... Args>
- auto Note(LocT loc, const DiagnosticBase<Args...>& diagnostic_base,
- Internal::NoTypeDeduction<Args>... args) -> DiagnosticBuilder& {
- if (!emitter_) {
- return *this;
- }
- CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note ||
- diagnostic_base.Level == DiagnosticLevel::LocationInfo,
- "{0}", static_cast<int>(diagnostic_base.Level));
- AddMessage(loc, diagnostic_base, {emitter_->MakeAny<Args>(args)...});
- return *this;
- }
- // Emits the built diagnostic and its attached notes.
- // For the expected usage see the builder API: `DiagnosticEmitter::Build`.
- template <typename... Args>
- auto Emit() -> void {
- if (!emitter_) {
- return;
- }
- for (auto annotate_fn : emitter_->annotate_fns_) {
- annotate_fn(*this);
- }
- emitter_->consumer_->HandleDiagnostic(std::move(diagnostic_));
- }
- // Returns true if this DiagnosticBuilder 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 DiagnosticEmitter<LocT>;
- template <typename... Args>
- explicit DiagnosticBuilder(DiagnosticEmitter<LocT>* emitter, LocT loc,
- const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args)
- : emitter_(emitter), diagnostic_({.level = diagnostic_base.Level}) {
- AddMessage(loc, diagnostic_base, std::move(args));
- CARBON_CHECK(diagnostic_base.Level != DiagnosticLevel::Note);
- }
- // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
- // be silently ignored.
- DiagnosticBuilder() : emitter_(nullptr) {}
- // 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 {
- if (!emitter_) {
- return;
- }
- AddMessageWithDiagnosticLoc(
- emitter_->converter_->ConvertLoc(
- loc,
- [&](DiagnosticLoc context_loc,
- const DiagnosticBase<>& context_diagnostic_base) {
- AddMessageWithDiagnosticLoc(context_loc,
- context_diagnostic_base, {});
- }),
- diagnostic_base, args);
- }
- // Adds a message to the diagnostic, handling conversion of the arguments. A
- // DiagnosticLoc must be provided instead of a LocT in order to
- // avoid potential recursion.
- template <typename... Args>
- auto AddMessageWithDiagnosticLoc(
- DiagnosticLoc loc, const DiagnosticBase<Args...>& diagnostic_base,
- llvm::SmallVector<llvm::Any> args) -> void {
- if (!emitter_) {
- return;
- }
- diagnostic_.messages.emplace_back(DiagnosticMessage{
- .kind = diagnostic_base.Kind,
- .level = diagnostic_base.Level,
- .loc = loc,
- .format = diagnostic_base.Format,
- .format_args = std::move(args),
- .format_fn = [](const DiagnosticMessage& message) -> std::string {
- return FormatFn<Args...>(
- message, std::make_index_sequence<sizeof...(Args)>());
- }});
- }
- // 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 DiagnosticMessage& 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])...);
- }
- DiagnosticEmitter<LocT>* emitter_;
- Diagnostic diagnostic_;
- };
- // The `converter` and `consumer` are required to outlive the diagnostic
- // emitter.
- explicit DiagnosticEmitter(DiagnosticConverter<LocT>& converter,
- DiagnosticConsumer& consumer)
- : converter_(&converter), consumer_(&consumer) {}
- ~DiagnosticEmitter() = 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 {
- DiagnosticBuilder(this, loc, diagnostic_base, {MakeAny<Args>(args)...})
- .Emit();
- }
- // 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) -> DiagnosticBuilder {
- return DiagnosticBuilder(this, loc, diagnostic_base,
- {MakeAny<Args>(args)...});
- }
- // Create a null `DiagnosticBuilder` that will not emit anything. Notes will
- // be silently ignored.
- auto BuildSuppressed() -> DiagnosticBuilder { return DiagnosticBuilder(); }
- 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 {
- llvm::Any converted = converter_->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;
- }
- template <typename OtherLocT, typename AnnotateFn>
- friend class DiagnosticAnnotationScope;
- DiagnosticConverter<LocT>* converter_;
- DiagnosticConsumer* consumer_;
- llvm::SmallVector<llvm::function_ref<auto(DiagnosticBuilder& builder)->void>>
- annotate_fns_;
- };
- // 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
- // `DiagnosticBuilder& 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 DiagnosticAnnotationScope {
- public:
- DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter,
- AnnotateFn annotate)
- : emitter_(emitter), annotate_(std::move(annotate)) {
- emitter_->annotate_fns_.push_back(annotate_);
- }
- ~DiagnosticAnnotationScope() { emitter_->annotate_fns_.pop_back(); }
- private:
- DiagnosticEmitter<LocT>* emitter_;
- // Make a copy of the annotation function to ensure that it lives long enough.
- AnnotateFn annotate_;
- };
- template <typename LocT, typename AnnotateFn>
- DiagnosticAnnotationScope(DiagnosticEmitter<LocT>* emitter, AnnotateFn annotate)
- -> DiagnosticAnnotationScope<LocT, AnnotateFn>;
- } // namespace Carbon
- #endif // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_
|