diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 5b5517a2726d..622a2d372c5a 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -26,6 +26,7 @@ cc_library( "cpp/constant.cpp", "cpp/context.cpp", "cpp/custom_type_mapping.cpp", + "cpp/diagnostic_consumer.cpp", "cpp/export.cpp", "cpp/generate_ast.cpp", "cpp/impl_lookup.cpp", @@ -90,6 +91,8 @@ cc_library( "cpp/constant.h", "cpp/context.h", "cpp/custom_type_mapping.h", + "cpp/diagnostic_consumer.h", + "cpp/diagnostic_listener.h", "cpp/export.h", "cpp/generate_ast.h", "cpp/impl_lookup.h", @@ -266,7 +269,9 @@ cc_library( "//toolchain/sem_ir:expr_info", "//toolchain/sem_ir:file", "//toolchain/sem_ir:formatter", + "//toolchain/sem_ir:read_only_ast_source", "//toolchain/sem_ir:typed_insts", + "@llvm-project//clang:ast", "@llvm-project//clang:frontend", "@llvm-project//clang:sema", "@llvm-project//llvm:Support", diff --git a/toolchain/check/cpp/context.cpp b/toolchain/check/cpp/context.cpp index 4593347bbc55..f82b1d4a22c3 100644 --- a/toolchain/check/cpp/context.cpp +++ b/toolchain/check/cpp/context.cpp @@ -18,6 +18,11 @@ CppContext::CppContext(clang::CompilerInstance& instance, CppContext::~CppContext() = default; +auto CppContext::set_diagnostic_listener( + std::unique_ptr listener) -> void { + diagnostic_listener_ = std::move(listener); +} + auto CppContext::clang_mangle_context() -> clang::MangleContext& { if (!clang_mangle_context_) { clang_mangle_context_.reset(ast_context().createMangleContext()); diff --git a/toolchain/check/cpp/context.h b/toolchain/check/cpp/context.h index 2ebc83ef1157..83424c61ec85 100644 --- a/toolchain/check/cpp/context.h +++ b/toolchain/check/cpp/context.h @@ -10,6 +10,7 @@ #include "clang/Basic/SourceLocation.h" #include "common/check.h" #include "llvm/ADT/SmallVector.h" +#include "toolchain/check/cpp/diagnostic_listener.h" namespace clang { class ASTContext; @@ -50,6 +51,12 @@ class CppContext { placement_new_decl_ = decl; } + auto diagnostic_listener() -> CppDiagnosticListener* { + return diagnostic_listener_.get(); + } + auto set_diagnostic_listener(std::unique_ptr listener) + -> void; + private: // The Clang AST context. clang::ASTContext* ast_context_; @@ -69,6 +76,9 @@ class CppContext { // The cached placement new function declaration. clang::FunctionDecl* placement_new_decl_ = nullptr; + + // Listener for Clang diagnostics while checking this Carbon context. + std::unique_ptr diagnostic_listener_; }; } // namespace Carbon::Check diff --git a/toolchain/check/cpp/diagnostic_consumer.cpp b/toolchain/check/cpp/diagnostic_consumer.cpp new file mode 100644 index 000000000000..726a8f54a32c --- /dev/null +++ b/toolchain/check/cpp/diagnostic_consumer.cpp @@ -0,0 +1,294 @@ +// 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 + +#include "toolchain/check/cpp/diagnostic_consumer.h" + +#include +#include + +#include "clang/Basic/Diagnostic.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/TextDiagnostic.h" +#include "common/check.h" +#include "common/raw_string_ostream.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "toolchain/check/context.h" +#include "toolchain/check/cpp/diagnostic_listener.h" +#include "toolchain/check/cpp/location.h" +#include "toolchain/diagnostics/diagnostic.h" +#include "toolchain/diagnostics/emitter.h" + +namespace Carbon::Check { + +class CarbonClangDiagnosticConsumer; + +// Returns the diagnostic to use for a given Clang diagnostic level. +static auto GetDiagnostic(clang::DiagnosticsEngine::Level level) + -> const Diagnostics::DiagnosticBase& { + switch (level) { + case clang::DiagnosticsEngine::Ignored: { + CARBON_FATAL("Emitting an ignored diagnostic"); + break; + } + case clang::DiagnosticsEngine::Note: { + CARBON_DIAGNOSTIC(CppInteropParseNote, Note, "{0}", std::string); + return CppInteropParseNote; + } + case clang::DiagnosticsEngine::Remark: + case clang::DiagnosticsEngine::Warning: { + // TODO: Add a distinct Remark level to Carbon diagnostics, and stop + // mapping remarks to warnings. + CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "{0}", std::string); + return CppInteropParseWarning; + } + case clang::DiagnosticsEngine::Error: + case clang::DiagnosticsEngine::Fatal: { + CARBON_DIAGNOSTIC(CppInteropParseError, Error, "{0}", std::string); + return CppInteropParseError; + } + } +} + +// A listener that emits Clang diagnostics directly to a provided Carbon +// Diagnostics::Consumer when no Carbon Context is active. +class FallbackDiagnosticListener : public CppDiagnosticListener { + public: + explicit FallbackDiagnosticListener(CarbonClangDiagnosticConsumer& consumer, + Diagnostics::Consumer& next_consumer) + : CppDiagnosticListener(consumer), next_consumer_(&next_consumer) {} + + ~FallbackDiagnosticListener() override; + + auto EmitDiagnostics(llvm::ArrayRef diags) -> void override { + if (diags.empty()) { + return; + } + Diagnostics::NoLocEmitter emitter(next_consumer_); + for (size_t i = 0; i != diags.size(); ++i) { + const Diagnostic& info = diags[i]; + auto builder = + emitter.Build(nullptr, GetDiagnostic(info.level), info.message); + builder.OverrideSnippet(info.snippet); + for (; i + 1 < diags.size() && + diags[i + 1].level == clang::DiagnosticsEngine::Note; + ++i) { + const Diagnostic& note_info = diags[i + 1]; + builder.Note(nullptr, GetDiagnostic(note_info.level), note_info.message) + .OverrideSnippet(note_info.snippet); + } + builder.Emit(); + } + } + + private: + Diagnostics::Consumer* next_consumer_; +}; + +// A listener that converts Clang diagnostics to Carbon diagnostics using a +// Carbon Context. +class ContextDiagnosticListener : public CppDiagnosticListener { + public: + explicit ContextDiagnosticListener(CarbonClangDiagnosticConsumer& consumer, + Context& context) + : CppDiagnosticListener(consumer), context_(&context) {} + + ~ContextDiagnosticListener() override; + + auto EmitDiagnostics(llvm::ArrayRef diags) -> void override { + if (diags.empty()) { + return; + } + CARBON_CHECK( + context_->sem_ir().cpp_file(), + "Attempted to emit C++ diagnostics before the C++ file is set"); + + for (size_t i = 0; i != diags.size(); ++i) { + const Diagnostic& info = diags[i]; + SemIR::ImportIRInstId import_ir_inst_id = + AddImportIRInst(context_->sem_ir(), info.location); + auto builder = + context_->emitter().Build(SemIR::LocId(import_ir_inst_id), + GetDiagnostic(info.level), info.message); + builder.OverrideSnippet(info.snippet); + for (; i + 1 < diags.size() && + diags[i + 1].level == clang::DiagnosticsEngine::Note; + ++i) { + const Diagnostic& note_info = diags[i + 1]; + SemIR::ImportIRInstId note_import_ir_inst_id = + AddImportIRInst(context_->sem_ir(), note_info.location); + builder + .Note(SemIR::LocId(note_import_ir_inst_id), + GetDiagnostic(note_info.level), note_info.message) + .OverrideSnippet(note_info.snippet); + } + // TODO: This will apply all current Carbon annotation functions. We + // should instead track how Clang's context notes and Carbon's annotation + // functions are interleaved, and interleave the notes in the same order. + builder.Emit(); + } + } + + private: + Context* context_; +}; + +// Used to convert Clang diagnostics to Carbon diagnostics. +// +// Handling of Clang notes is a little subtle: as far as Clang is concerned, +// notes are separate diagnostics, not connected to the error or warning that +// precedes them. But in Carbon's diagnostics system, notes are part of the +// enclosing diagnostic. To handle this, listeners buffer Clang diagnostics +// until we reach a point where we know we're not in the middle of a diagnostic, +// and then emit a diagnostic along with all of its notes. +class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer { + public: + explicit CarbonClangDiagnosticConsumer( + Diagnostics::Consumer& consumer, + std::shared_ptr invocation) + : fallback_listener_(*this, consumer), + invocation_(std::move(invocation)) {} + + ~CarbonClangDiagnosticConsumer() override { + CARBON_CHECK(diagnostic_infos_.empty(), "Missing flush before destruction"); + CARBON_CHECK(listeners_.size() == 1, + "Diagnostic listeners were not properly popped"); + } + + // Pushes a listener onto the stack. Diagnostics will be forwarded to the + // innermost listener. + auto PushListener(CppDiagnosticListener* listener) -> void { + CARBON_CHECK(diagnostic_infos_.empty(), + "Missing flush before pushing listener"); + CARBON_CHECK(listener); + listeners_.push_back(listener); + } + + // Pops a listener from the stack. + auto PopListener(CppDiagnosticListener* listener) -> void { + CARBON_CHECK(diagnostic_infos_.empty(), + "Missing flush before popping listener"); + CARBON_CHECK(!listeners_.empty() && listeners_.back() == listener, + "Popping unexpected diagnostic listener"); + listeners_.pop_back(); + } + + // Flushes the innermost listener. + auto Flush() -> void { + if (diagnostic_infos_.empty()) { + return; + } + CARBON_CHECK(!listeners_.empty(), "No diagnostic listeners registered"); + listeners_.back()->EmitDiagnostics(diagnostic_infos_); + diagnostic_infos_.clear(); + } + + auto HandleDiagnostic(clang::DiagnosticsEngine::Level diag_level, + const clang::Diagnostic& info) -> void override { + DiagnosticConsumer::HandleDiagnostic(diag_level, info); + + if (diag_level != clang::DiagnosticsEngine::Note && + !diagnostic_infos_.empty()) { + Flush(); + } + + llvm::SmallString<256> message; + info.FormatDiagnostic(message); + + // Render a code snippet including any highlighted ranges and fixit hints. + // TODO: Also include the #include stack and macro expansion stack in the + // diagnostic output in some way. + RawStringOstream snippet_stream; + if (!info.hasSourceManager()) { + // If we don't have a source manager, this is an error from early in the + // frontend. Don't produce a snippet. + CARBON_CHECK(info.getLocation().isInvalid()); + } else { + CodeContextRenderer(snippet_stream, invocation_->getLangOpts(), + invocation_->getDiagnosticOpts()) + .emitDiagnostic( + clang::FullSourceLoc(info.getLocation(), info.getSourceManager()), + diag_level, message, info.getRanges(), info.getFixItHints()); + } + + diagnostic_infos_.push_back({.level = diag_level, + .location = info.getLocation(), + .message = message.str().str(), + .snippet = snippet_stream.TakeStr()}); + } + + private: + // A diagnostics renderer based on clang's TextDiagnostic that captures just + // the code context (the snippet). + class CodeContextRenderer : public clang::TextDiagnostic { + protected: + using TextDiagnostic::TextDiagnostic; + + void emitDiagnosticMessage( + clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/, + clang::DiagnosticsEngine::Level /*level*/, llvm::StringRef /*message*/, + llvm::ArrayRef /*ranges*/, + clang::DiagOrStoredDiag /*info*/) override {} + void emitDiagnosticLoc( + clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/, + clang::DiagnosticsEngine::Level /*level*/, + llvm::ArrayRef /*ranges*/) override {} + + // emitCodeContext is inherited from clang::TextDiagnostic. + + void emitIncludeLocation(clang::FullSourceLoc /*loc*/, + clang::PresumedLoc /*ploc*/) override {} + void emitImportLocation(clang::FullSourceLoc /*loc*/, + clang::PresumedLoc /*ploc*/, + llvm::StringRef /*module_name*/) override {} + void emitBuildingModuleLocation(clang::FullSourceLoc /*loc*/, + clang::PresumedLoc /*ploc*/, + llvm::StringRef /*module_name*/) override {} + + // beginDiagnostic and endDiagnostic are inherited from + // clang::TextDiagnostic in case it wants to do any setup / teardown work. + }; + + llvm::SmallVector diagnostic_infos_; + llvm::SmallVector listeners_; + FallbackDiagnosticListener fallback_listener_; + std::shared_ptr invocation_; +}; + +FallbackDiagnosticListener::~FallbackDiagnosticListener() { + consumer().Flush(); +} + +ContextDiagnosticListener::~ContextDiagnosticListener() { consumer().Flush(); } + +CppDiagnosticListener::CppDiagnosticListener( + CarbonClangDiagnosticConsumer& consumer) + : consumer_(&consumer) { + consumer_->PushListener(this); +} + +CppDiagnosticListener::~CppDiagnosticListener() { + consumer_->PopListener(this); +} + +auto MakeDiagnosticConsumer( + Diagnostics::Consumer& consumer, + std::shared_ptr invocation) + -> std::unique_ptr { + return std::make_unique(consumer, + std::move(invocation)); +} + +auto MakeContextDiagnosticListener(clang::DiagnosticConsumer& consumer, + Context& context) + -> std::unique_ptr { + auto* clang_consumer = static_cast(&consumer); + auto listener = + std::make_unique(*clang_consumer, context); + context.emitter().AddFlushFn([clang_consumer] { clang_consumer->Flush(); }); + return listener; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/cpp/diagnostic_consumer.h b/toolchain/check/cpp/diagnostic_consumer.h new file mode 100644 index 000000000000..3b51574164e6 --- /dev/null +++ b/toolchain/check/cpp/diagnostic_consumer.h @@ -0,0 +1,40 @@ +// 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_CHECK_CPP_DIAGNOSTIC_CONSUMER_H_ +#define CARBON_TOOLCHAIN_CHECK_CPP_DIAGNOSTIC_CONSUMER_H_ + +#include + +#include "clang/Basic/Diagnostic.h" + +namespace clang { +class CompilerInvocation; +class DiagnosticConsumer; +} // namespace clang + +namespace Carbon::Diagnostics { +class Consumer; +} // namespace Carbon::Diagnostics + +namespace Carbon::Check { + +class Context; +class CppDiagnosticListener; + +// Creates a Clang DiagnosticConsumer that adapts Clang diagnostics to Carbon. +auto MakeDiagnosticConsumer( + Diagnostics::Consumer& consumer, + std::shared_ptr invocation) + -> std::unique_ptr; + +// Creates a diagnostic listener attached to the given Carbon context. The +// returned listener must not outlive the context. +auto MakeContextDiagnosticListener(clang::DiagnosticConsumer& consumer, + Context& context) + -> std::unique_ptr; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_CPP_DIAGNOSTIC_CONSUMER_H_ diff --git a/toolchain/check/cpp/diagnostic_listener.h b/toolchain/check/cpp/diagnostic_listener.h new file mode 100644 index 000000000000..08786133a348 --- /dev/null +++ b/toolchain/check/cpp/diagnostic_listener.h @@ -0,0 +1,52 @@ +// 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_CHECK_CPP_DIAGNOSTIC_LISTENER_H_ +#define CARBON_TOOLCHAIN_CHECK_CPP_DIAGNOSTIC_LISTENER_H_ + +#include + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/ArrayRef.h" + +namespace Carbon::Check { + +class CarbonClangDiagnosticConsumer; + +// Interface for listening to Clang diagnostics from +// CarbonClangDiagnosticConsumer. +// +// Pushes itself onto the consumer's listener stack on construction and pops +// itself on destruction. +class CppDiagnosticListener { + public: + struct Diagnostic { + clang::DiagnosticsEngine::Level level; + clang::SourceLocation location; + std::string message; + std::string snippet; + }; + + explicit CppDiagnosticListener(CarbonClangDiagnosticConsumer& consumer); + virtual ~CppDiagnosticListener(); + + // Emits a group of buffered diagnostics, which will comprise a single leading + // diagnostic -- typically an error / warning / remark -- followed by a + // sequence of attached notes. The first diagnostic can also be a note if the + // diagnostics engine is flushed in the middle of Clang emitting a diagnostic, + // or if Clang emits a "stray" note not attached to any diagnostic. Both would + // generally indicate Clang bugs. + virtual auto EmitDiagnostics(llvm::ArrayRef diags) -> void = 0; + + protected: + auto consumer() -> CarbonClangDiagnosticConsumer& { return *consumer_; } + + private: + CarbonClangDiagnosticConsumer* consumer_; +}; + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_CPP_DIAGNOSTIC_LISTENER_H_ diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index f2023c689228..19e369a1480c 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -9,6 +9,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/Basic/DiagnosticParse.h" #include "clang/Basic/FileManager.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/CompilerInstance.h" @@ -30,6 +31,8 @@ #include "toolchain/base/kind_switch.h" #include "toolchain/check/context.h" #include "toolchain/check/cpp/access.h" +#include "toolchain/check/cpp/diagnostic_consumer.h" +#include "toolchain/check/cpp/diagnostic_listener.h" #include "toolchain/check/cpp/export.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/cpp/location.h" @@ -107,204 +110,8 @@ static auto GenerateCppIncludesHeaderCode( return code_stream.TakeStr(); } -// Adds the given source location and an `ImportIRInst` referring to it in -// `ImportIRId::Cpp`. -static auto AddImportIRInst(SemIR::File& file, - clang::SourceLocation clang_source_loc) - -> SemIR::ImportIRInstId { - SemIR::ClangSourceLocId clang_source_loc_id = - file.clang_source_locs().Add(clang_source_loc); - return file.import_ir_insts().Add(SemIR::ImportIRInst(clang_source_loc_id)); -} - namespace { -// Used to convert Clang diagnostics to Carbon diagnostics. -// -// Handling of Clang notes is a little subtle: as far as Clang is concerned, -// notes are separate diagnostics, not connected to the error or warning that -// precedes them. But in Carbon's diagnostics system, notes are part of the -// enclosing diagnostic. To handle this, we buffer Clang diagnostics until we -// reach a point where we know we're not in the middle of a diagnostic, and then -// emit a diagnostic along with all of its notes. This is triggered when adding -// or removing a Carbon context note, which could otherwise get attached to the -// wrong C++ diagnostics, and at the end of the Carbon program. -class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer { - public: - // Creates an instance with the location that triggers calling Clang. The - // `context` is not stored here, and the diagnostics consumer is expected to - // outlive it. - explicit CarbonClangDiagnosticConsumer( - Context& context, std::shared_ptr invocation) - : sem_ir_(&context.sem_ir()), - emitter_(&context.emitter()), - invocation_(std::move(invocation)) { - emitter_->AddFlushFn([this] { EmitDiagnostics(); }); - } - - ~CarbonClangDiagnosticConsumer() override { - // Do not inspect `emitter_` here; it's typically destroyed before the - // consumer is. - // TODO: If Clang produces diagnostics after check finishes, they'll get - // added to the list of pending diagnostics and never emitted. - CARBON_CHECK(diagnostic_infos_.empty(), - "Missing flush before destroying diagnostic consumer"); - } - - // Generates a Carbon warning for each Clang warning and a Carbon error for - // each Clang error or fatal. - auto HandleDiagnostic(clang::DiagnosticsEngine::Level diag_level, - const clang::Diagnostic& info) -> void override { - DiagnosticConsumer::HandleDiagnostic(diag_level, info); - - SemIR::ImportIRInstId clang_import_ir_inst_id = - AddImportIRInst(*sem_ir_, info.getLocation()); - - llvm::SmallString<256> message; - info.FormatDiagnostic(message); - - // Render a code snippet including any highlighted ranges and fixit hints. - // TODO: Also include the #include stack and macro expansion stack in the - // diagnostic output in some way. - RawStringOstream snippet_stream; - if (!info.hasSourceManager()) { - // If we don't have a source manager, this is an error from early in the - // frontend. Don't produce a snippet. - CARBON_CHECK(info.getLocation().isInvalid()); - } else { - CodeContextRenderer(snippet_stream, invocation_->getLangOpts(), - invocation_->getDiagnosticOpts()) - .emitDiagnostic( - clang::FullSourceLoc(info.getLocation(), info.getSourceManager()), - diag_level, message, info.getRanges(), info.getFixItHints()); - } - - diagnostic_infos_.push_back({.level = diag_level, - .import_ir_inst_id = clang_import_ir_inst_id, - .message = message.str().str(), - .snippet = snippet_stream.TakeStr()}); - } - - // Returns the diagnostic to use for a given Clang diagnostic level. - static auto GetDiagnostic(clang::DiagnosticsEngine::Level level) - -> const Diagnostics::DiagnosticBase& { - switch (level) { - case clang::DiagnosticsEngine::Ignored: { - CARBON_FATAL("Emitting an ignored diagnostic"); - break; - } - case clang::DiagnosticsEngine::Note: { - CARBON_DIAGNOSTIC(CppInteropParseNote, Note, "{0}", std::string); - return CppInteropParseNote; - } - case clang::DiagnosticsEngine::Remark: - case clang::DiagnosticsEngine::Warning: { - // TODO: Add a distinct Remark level to Carbon diagnostics, and stop - // mapping remarks to warnings. - CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning, "{0}", std::string); - return CppInteropParseWarning; - } - case clang::DiagnosticsEngine::Error: - case clang::DiagnosticsEngine::Fatal: { - CARBON_DIAGNOSTIC(CppInteropParseError, Error, "{0}", std::string); - return CppInteropParseError; - } - } - } - - // Outputs Carbon diagnostics based on the collected Clang diagnostics. Must - // be called after the AST is set in the context. - auto EmitDiagnostics() -> void { - CARBON_CHECK( - sem_ir_->cpp_file(), - "Attempted to emit C++ diagnostics before the C++ file is set"); - - for (size_t i = 0; i != diagnostic_infos_.size(); ++i) { - const ClangDiagnosticInfo& info = diagnostic_infos_[i]; - auto builder = emitter_->Build(SemIR::LocId(info.import_ir_inst_id), - GetDiagnostic(info.level), info.message); - builder.OverrideSnippet(info.snippet); - for (; i + 1 < diagnostic_infos_.size() && - diagnostic_infos_[i + 1].level == clang::DiagnosticsEngine::Note; - ++i) { - const ClangDiagnosticInfo& note_info = diagnostic_infos_[i + 1]; - builder - .Note(SemIR::LocId(note_info.import_ir_inst_id), - GetDiagnostic(note_info.level), note_info.message) - .OverrideSnippet(note_info.snippet); - } - // TODO: This will apply all current Carbon annotation functions. We - // should instead track how Clang's context notes and Carbon's annotation - // functions are interleaved, and interleave the notes in the same order. - builder.Emit(); - } - diagnostic_infos_.clear(); - } - - private: - // A diagnostics renderer based on clang's TextDiagnostic that captures just - // the code context (the snippet). - class CodeContextRenderer : public clang::TextDiagnostic { - protected: - using TextDiagnostic::TextDiagnostic; - - void emitDiagnosticMessage( - clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/, - clang::DiagnosticsEngine::Level /*level*/, llvm::StringRef /*message*/, - llvm::ArrayRef /*ranges*/, - clang::DiagOrStoredDiag /*info*/) override {} - void emitDiagnosticLoc( - clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/, - clang::DiagnosticsEngine::Level /*level*/, - llvm::ArrayRef /*ranges*/) override {} - - // emitCodeContext is inherited from clang::TextDiagnostic. - - void emitIncludeLocation(clang::FullSourceLoc /*loc*/, - clang::PresumedLoc /*ploc*/) override {} - void emitImportLocation(clang::FullSourceLoc /*loc*/, - clang::PresumedLoc /*ploc*/, - llvm::StringRef /*module_name*/) override {} - void emitBuildingModuleLocation(clang::FullSourceLoc /*loc*/, - clang::PresumedLoc /*ploc*/, - llvm::StringRef /*module_name*/) override {} - - // beginDiagnostic and endDiagnostic are inherited from - // clang::TextDiagnostic in case it wants to do any setup / teardown work. - }; - - // Information on a Clang diagnostic that can be converted to a Carbon - // diagnostic. - struct ClangDiagnosticInfo { - // The Clang diagnostic level. - clang::DiagnosticsEngine::Level level; - - // The ID of the ImportIR instruction referring to the Clang source - // location. - SemIR::ImportIRInstId import_ir_inst_id; - - // The Clang diagnostic textual message. - std::string message; - - // The code snippet produced by clang. - std::string snippet; - }; - - // The Carbon file that this C++ compilation is attached to. - SemIR::File* sem_ir_; - - // The diagnostic emitter that we're emitting diagnostics into. - DiagnosticEmitterBase* emitter_; - - // The compiler invocation that is producing the diagnostics. - std::shared_ptr invocation_; - - // Collects the information for all Clang diagnostics to be converted to - // Carbon diagnostics after the context has been initialized with the Clang - // AST. - llvm::SmallVector diagnostic_infos_; -}; - // A wrapper around a clang::CompilerInvocation that allows us to make a shallow // copy of most of the invocation and only make a deep copy of the parts that we // want to change. @@ -398,11 +205,7 @@ class CarbonExternalASTSource : public SemIR::ReadOnlyASTSource { clang_source_loc = code_synthesis_contexts.back().PointOfInstantiation; } - // TODO: Refactor with AddImportIRInst in import.cpp. - SemIR::ClangSourceLocId clang_source_loc_id = - context_->sem_ir().clang_source_locs().Add(clang_source_loc); - return context_->import_ir_insts().Add( - SemIR::ImportIRInst(clang_source_loc_id)); + return AddImportIRInst(context_->sem_ir(), clang_source_loc); } // For LLVM RTTI. @@ -813,7 +616,11 @@ namespace { // from a set of Cpp imports. class GenerateASTAction : public clang::ASTFrontendAction { public: - explicit GenerateASTAction(Context& context) : context_(&context) {} + explicit GenerateASTAction( + Context& context, + std::unique_ptr diagnostic_listener) + : context_(&context), + diagnostic_listener_(std::move(diagnostic_listener)) {} protected: auto CreateASTConsumer(clang::CompilerInstance& clang_instance, @@ -858,8 +665,10 @@ class GenerateASTAction : public clang::ASTFrontendAction { clang_instance.getPreprocessor().EnterMainSourceFile(); parser.Initialize(); - context_->set_cpp_context( - std::make_unique(clang_instance, std::move(parser_ptr))); + auto cpp_context = + std::make_unique(clang_instance, std::move(parser_ptr)); + cpp_context->set_diagnostic_listener(std::move(diagnostic_listener_)); + context_->set_cpp_context(std::move(cpp_context)); if (auto* source = clang_instance.getASTContext().getExternalSource()) { source->StartTranslationUnit(&clang_instance.getASTConsumer()); @@ -872,6 +681,7 @@ class GenerateASTAction : public clang::ASTFrontendAction { private: Context* context_; + std::unique_ptr diagnostic_listener_; }; } // namespace @@ -891,11 +701,14 @@ auto GenerateAst(Context& context, // Ask Clang to not leak memory. invocation->getFrontendOpts().DisableFree = false; + auto diagnostic_consumer = + MakeDiagnosticConsumer(context.emitter().consumer(), invocation); + auto* diagnostic_consumer_ptr = diagnostic_consumer.get(); + // Build a diagnostics engine. llvm::IntrusiveRefCntPtr diags( clang::CompilerInstance::createDiagnostics( - *fs, invocation->getDiagnosticOpts(), - new CarbonClangDiagnosticConsumer(context, invocation), + *fs, invocation->getDiagnosticOpts(), diagnostic_consumer.release(), /*ShouldOwnClient=*/true)); // Extract the input from the frontend invocation and make sure it makes @@ -936,7 +749,8 @@ auto GenerateAst(Context& context, return false; } - GenerateASTAction action(context); + GenerateASTAction action(context, MakeContextDiagnosticListener( + *diagnostic_consumer_ptr, context)); if (!action.BeginSourceFile(clang_instance, inputs[0])) { return false; } @@ -1017,6 +831,7 @@ auto FinishAst(Context& context) -> void { } context.cpp_context()->sema().ActOnEndOfTranslationUnit(); + context.emitter().Flush(); // Remove the `CarbonExternalASTSource` installed in `GenerateAst` and // replace it with a `ReadOnlyASTSource`. This is necessary because @@ -1034,8 +849,6 @@ auto FinishAst(Context& context) -> void { // We don't call FrontendAction::EndSourceFile, because that destroys the AST. context.set_cpp_context(nullptr); - - context.emitter().Flush(); } } // namespace Carbon::Check diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index 7b1261e125b5..f417ff7f3f7e 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -96,16 +96,6 @@ auto AddIdentifierName(Context& context, llvm::StringRef name) return SemIR::NameId::ForIdentifier(context.identifiers().Add(name)); } -// Adds the given source location and an `ImportIRInst` referring to it in -// `ImportIRId::Cpp`. -static auto AddImportIRInst(SemIR::File& file, - clang::SourceLocation clang_source_loc) - -> SemIR::ImportIRInstId { - SemIR::ClangSourceLocId clang_source_loc_id = - file.clang_source_locs().Add(clang_source_loc); - return file.import_ir_insts().Add(SemIR::ImportIRInst(clang_source_loc_id)); -} - // Adds a namespace for the `Cpp` import and returns its `NameScopeId`. static auto AddNamespace(Context& context, PackageNameId cpp_package_id, llvm::ArrayRef imports) diff --git a/toolchain/check/cpp/location.cpp b/toolchain/check/cpp/location.cpp index ca39927570ee..2f0e3e69e470 100644 --- a/toolchain/check/cpp/location.cpp +++ b/toolchain/check/cpp/location.cpp @@ -88,4 +88,11 @@ auto GetCppLocation(Context& context, SemIR::LocId loc_id) return start_loc.getLocWithOffset(offset); } +auto AddImportIRInst(SemIR::File& file, clang::SourceLocation clang_source_loc) + -> SemIR::ImportIRInstId { + SemIR::ClangSourceLocId clang_source_loc_id = + file.clang_source_locs().Add(clang_source_loc); + return file.import_ir_insts().Add(SemIR::ImportIRInst(clang_source_loc_id)); +} + } // namespace Carbon::Check diff --git a/toolchain/check/cpp/location.h b/toolchain/check/cpp/location.h index 85e76e1ec5f0..021059f9fc90 100644 --- a/toolchain/check/cpp/location.h +++ b/toolchain/check/cpp/location.h @@ -14,6 +14,11 @@ namespace Carbon::Check { auto GetCppLocation(Context& context, SemIR::LocId loc_id) -> clang::SourceLocation; +// Adds an `ImportIRInst` referring to the given source location and returns a +// corresponding `ImportIRInstId` that can be used to construct a `LocId`. +auto AddImportIRInst(SemIR::File& file, clang::SourceLocation clang_source_loc) + -> SemIR::ImportIRInstId; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_CPP_LOCATION_H_ diff --git a/toolchain/diagnostics/emitter.h b/toolchain/diagnostics/emitter.h index 4ab7799fd381..d8b478b76fe5 100644 --- a/toolchain/diagnostics/emitter.h +++ b/toolchain/diagnostics/emitter.h @@ -219,6 +219,9 @@ class Emitter { // but that would be highly unusual. auto CheckHasContext() -> void { CARBON_CHECK(!context_fns_.empty()); } + // Returns the consumer for this emitter. + auto consumer() const -> Consumer& { return *consumer_; } + protected: // Callback type used to report context messages from ConvertLoc. // Note that the first parameter type is Loc rather than