mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
Instead of treating `Core.Int` as the toolchain's builtin `IntType`, model it as a class that adapts the builtin type. This aligns us better with the intended language model, gives an associated library for `impl`s involving `Core.Int` to live within, and opens the door adding member functions to `Core.Int` if we decide that is desirable. Remarkably it also seems to make the formatted SemIR a little smaller, because a call to a generic class generates less IR than a call to a function.
260 lines
10 KiB
C++
260 lines
10 KiB
C++
// 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 : 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<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_
|