// 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 #include #include #include #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 using NoTypeDeduction = std::type_identity_t; } // namespace Internal template 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 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 auto Note(LocT loc, const DiagnosticBase& diagnostic_base, Internal::NoTypeDeduction... args) -> DiagnosticBuilder& { if (!emitter_) { return *this; } CARBON_CHECK(diagnostic_base.Level == DiagnosticLevel::Note || diagnostic_base.Level == DiagnosticLevel::LocationInfo, "{0}", static_cast(diagnostic_base.Level)); AddMessage(loc, diagnostic_base, {emitter_->MakeAny(args)...}); return *this; } // Emits the built diagnostic and its attached notes. // For the expected usage see the builder API: `DiagnosticEmitter::Build`. template auto Emit() -> void { if (!emitter_) { return; } for (auto annotate_fn : llvm::reverse(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; template explicit DiagnosticBuilder(DiagnosticEmitter* emitter, LocT loc, const DiagnosticBase& diagnostic_base, llvm::SmallVector 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 auto AddMessage(LocT loc, const DiagnosticBase& diagnostic_base, llvm::SmallVector 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 auto AddMessageWithDiagnosticLoc( DiagnosticLoc loc, const DiagnosticBase& diagnostic_base, llvm::SmallVector 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( message, std::make_index_sequence()); }}); } // 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 static auto FormatFn(const DiagnosticMessage& message, std::index_sequence /*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::StorageType>( message.format_args[N])...); } DiagnosticEmitter* emitter_; Diagnostic diagnostic_; }; // The `converter` and `consumer` are required to outlive the diagnostic // emitter. explicit DiagnosticEmitter(DiagnosticConverter& 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 auto Emit(LocT loc, const DiagnosticBase& diagnostic_base, Internal::NoTypeDeduction... args) -> void { DiagnosticBuilder(this, loc, diagnostic_base, {MakeAny(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 auto Build(LocT loc, const DiagnosticBase& diagnostic_base, Internal::NoTypeDeduction... args) -> DiagnosticBuilder { return DiagnosticBuilder(this, loc, diagnostic_base, {MakeAny(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 auto MakeAny(Arg arg) -> llvm::Any { llvm::Any converted = converter_->ConvertArg(arg); using Storage = Internal::DiagnosticTypeForArg::StorageType; CARBON_CHECK( llvm::any_cast(&converted), "Failed to convert argument of type {0} to its storage type {1}", typeid(Arg).name(), typeid(Storage).name()); return converted; } template friend class DiagnosticAnnotationScope; DiagnosticConverter* converter_; DiagnosticConsumer* consumer_; llvm::SmallVectorvoid>> 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 class DiagnosticAnnotationScope { public: DiagnosticAnnotationScope(DiagnosticEmitter* emitter, AnnotateFn annotate) : emitter_(emitter), annotate_(std::move(annotate)) { emitter_->annotate_fns_.push_back(annotate_); } ~DiagnosticAnnotationScope() { emitter_->annotate_fns_.pop_back(); } private: DiagnosticEmitter* emitter_; // Make a copy of the annotation function to ensure that it lives long enough. AnnotateFn annotate_; }; template DiagnosticAnnotationScope(DiagnosticEmitter* emitter, AnnotateFn annotate) -> DiagnosticAnnotationScope; } // namespace Carbon #endif // CARBON_TOOLCHAIN_DIAGNOSTICS_DIAGNOSTIC_EMITTER_H_