mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Factor out a Check::CppContext holding C++-specific check state (#6482)
* Move `Sema` access from `CppFile` into `CppContext`. * Move the mangle context from `SemIR::File` into `CppContext`. * Move source location mapping state from `Context` into `CppContext`. Also factor out the `GenerateAst` function that builds the `CppContext` and `CppFile` into its own file.
This commit is contained in:
@@ -15,18 +15,13 @@
|
||||
#include "clang/AST/RecordLayout.h"
|
||||
#include "clang/AST/UnresolvedSet.h"
|
||||
#include "clang/AST/VTableBuilder.h"
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
#include "clang/Frontend/CompilerInvocation.h"
|
||||
#include "clang/Frontend/TextDiagnostic.h"
|
||||
#include "clang/Lex/PreprocessorOptions.h"
|
||||
#include "clang/Sema/Lookup.h"
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "common/check.h"
|
||||
#include "common/ostream.h"
|
||||
#include "common/raw_string_ostream.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "toolchain/base/int.h"
|
||||
@@ -39,6 +34,7 @@
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/cpp/access.h"
|
||||
#include "toolchain/check/cpp/custom_type_mapping.h"
|
||||
#include "toolchain/check/cpp/generate_ast.h"
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/cpp/macros.h"
|
||||
#include "toolchain/check/cpp/thunk.h"
|
||||
@@ -55,9 +51,6 @@
|
||||
#include "toolchain/check/pattern_match.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/check/type_completion.h"
|
||||
#include "toolchain/diagnostics/diagnostic.h"
|
||||
#include "toolchain/diagnostics/diagnostic_emitter.h"
|
||||
#include "toolchain/diagnostics/format_providers.h"
|
||||
#include "toolchain/parse/node_ids.h"
|
||||
#include "toolchain/sem_ir/clang_decl.h"
|
||||
#include "toolchain/sem_ir/class.h"
|
||||
@@ -71,88 +64,6 @@
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Add a line marker directive pointing at the location of the `import Cpp`
|
||||
// declaration in the Carbon source file. This will cause Clang's diagnostics
|
||||
// machinery to track and report the location in Carbon code where the import
|
||||
// was written.
|
||||
static auto GenerateLineMarker(Context& context, llvm::raw_ostream& out,
|
||||
int line) {
|
||||
out << "# " << line << " \""
|
||||
<< FormatEscaped(context.tokens().source().filename()) << "\"\n";
|
||||
}
|
||||
|
||||
// Generates C++ file contents to #include all requested imports.
|
||||
static auto GenerateCppIncludesHeaderCode(
|
||||
Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
|
||||
-> std::string {
|
||||
std::string code;
|
||||
llvm::raw_string_ostream code_stream(code);
|
||||
for (const Parse::Tree::PackagingNames& import : imports) {
|
||||
if (import.inline_body_id.has_value()) {
|
||||
// Expand `import Cpp inline "code";` directly into the specified code.
|
||||
auto code_token = context.parse_tree().node_token(import.inline_body_id);
|
||||
|
||||
// Compute the line number on which the C++ code starts. Usually the code
|
||||
// is specified as a block string literal and starts on the line after the
|
||||
// start of the string token.
|
||||
// TODO: Determine if this is a block string literal without calling
|
||||
// `GetTokenText`, which re-lexes the string.
|
||||
int line = context.tokens().GetLineNumber(code_token);
|
||||
if (context.tokens().GetTokenText(code_token).contains('\n')) {
|
||||
++line;
|
||||
}
|
||||
|
||||
GenerateLineMarker(context, code_stream, line);
|
||||
code_stream << context.string_literal_values().Get(
|
||||
context.tokens().GetStringLiteralValue(code_token))
|
||||
<< "\n";
|
||||
// TODO: Inject a clang pragma here to produce an error if there are
|
||||
// unclosed scopes at the end of this inline C++ fragment.
|
||||
} else if (import.library_id.has_value()) {
|
||||
// Translate `import Cpp library "foo.h";` into `#include "foo.h"`.
|
||||
GenerateLineMarker(context, code_stream,
|
||||
context.tokens().GetLineNumber(
|
||||
context.parse_tree().node_token(import.node_id)));
|
||||
auto name = context.string_literal_values().Get(import.library_id);
|
||||
if (name.starts_with('<') && name.ends_with('>')) {
|
||||
code_stream << "#include <"
|
||||
<< FormatEscaped(name.drop_front().drop_back()) << ">\n";
|
||||
} else {
|
||||
code_stream << "#include \"" << FormatEscaped(name) << "\"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inject a declaration of placement operator new, because the code we
|
||||
// generate in thunks depends on it for placement new expressions. Clang has
|
||||
// special-case logic for lowering a new-expression using this, so a
|
||||
// definition is not required.
|
||||
// TODO: This is a hack. We should be able to directly generate Clang AST to
|
||||
// construct objects in-place without this.
|
||||
// TODO: Once we can rely on libc++ being available, consider including
|
||||
// `<__new/placement_new_delete.h>` instead.
|
||||
code_stream << R"(# 1 "<carbon-internal>"
|
||||
#undef constexpr
|
||||
#if __cplusplus > 202302L
|
||||
constexpr
|
||||
#endif
|
||||
#undef void
|
||||
#undef operator
|
||||
#undef new
|
||||
void* operator new(__SIZE_TYPE__, void*)
|
||||
#if __cplusplus < 201103L
|
||||
#undef throw
|
||||
throw()
|
||||
#else
|
||||
#undef noexcept
|
||||
noexcept
|
||||
#endif
|
||||
;
|
||||
)";
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
// Adds the name to the scope with the given `access_kind` and `inst_id`.
|
||||
// `inst_id` must have a value.
|
||||
static auto AddNameToScope(Context& context, SemIR::NameScopeId scope_id,
|
||||
@@ -180,269 +91,6 @@ static auto AddImportIRInst(SemIR::File& file,
|
||||
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<clang::CompilerInvocation> 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<std::string>& {
|
||||
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<clang::CharSourceRange> /*ranges*/,
|
||||
clang::DiagOrStoredDiag /*info*/) override {}
|
||||
void emitDiagnosticLoc(
|
||||
clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
|
||||
clang::DiagnosticsEngine::Level /*level*/,
|
||||
llvm::ArrayRef<clang::CharSourceRange> /*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<clang::CompilerInvocation> 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<ClangDiagnosticInfo> 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.
|
||||
//
|
||||
// clang::CowCompilerInvocation almost allows this, but doesn't derive from
|
||||
// CompilerInvocation or support shallow copies from a CompilerInvocation, so is
|
||||
// not useful to us as we can't build an ASTUnit from it.
|
||||
class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
|
||||
public:
|
||||
explicit ShallowCopyCompilerInvocation(
|
||||
const clang::CompilerInvocation& invocation) {
|
||||
shallow_copy_assign(invocation);
|
||||
|
||||
// The preprocessor options are modified to hold a replacement includes
|
||||
// buffer, so make our own version of those options.
|
||||
PPOpts = std::make_shared<clang::PreprocessorOptions>(*PPOpts);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Generates a Clang AST for the C++ imports and sets it in the context's
|
||||
// `sem_ir`. Returns a bool that represents whether compilation was successful.
|
||||
// TODO: Consider to always have a (non-null) AST even if there are no Cpp
|
||||
// imports.
|
||||
static auto GenerateAst(
|
||||
Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
|
||||
std::shared_ptr<clang::CompilerInvocation> base_invocation) -> bool {
|
||||
auto invocation =
|
||||
std::make_shared<ShallowCopyCompilerInvocation>(*base_invocation);
|
||||
|
||||
// Build a diagnostics engine.
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags(
|
||||
clang::CompilerInstance::createDiagnostics(
|
||||
*fs, invocation->getDiagnosticOpts(),
|
||||
new CarbonClangDiagnosticConsumer(context, invocation),
|
||||
/*ShouldOwnClient=*/true));
|
||||
|
||||
// Extract the input from the frontend invocation and make sure it makes
|
||||
// sense.
|
||||
const auto& inputs = invocation->getFrontendOpts().Inputs;
|
||||
CARBON_CHECK(inputs.size() == 1 &&
|
||||
inputs[0].getKind().getLanguage() == clang::Language::CXX &&
|
||||
inputs[0].getKind().getFormat() == clang::InputKind::Source);
|
||||
llvm::StringRef file_name = inputs[0].getFile();
|
||||
|
||||
// Remap the imports file name to the corresponding `#include`s.
|
||||
// TODO: Modify the frontend options to specify this memory buffer as input
|
||||
// instead of remapping the file.
|
||||
std::string includes = GenerateCppIncludesHeaderCode(context, imports);
|
||||
auto includes_buffer =
|
||||
llvm::MemoryBuffer::getMemBufferCopy(includes, file_name);
|
||||
invocation->getPreprocessorOpts().addRemappedFile(file_name,
|
||||
includes_buffer.release());
|
||||
|
||||
clang::DiagnosticErrorTrap trap(*diags);
|
||||
|
||||
// Create the AST unit.
|
||||
auto ast = clang::ASTUnit::LoadFromCompilerInvocation(
|
||||
invocation, std::make_shared<clang::PCHContainerOperations>(), nullptr,
|
||||
diags, new clang::FileManager(invocation->getFileSystemOpts(), fs));
|
||||
|
||||
// Attach the AST to SemIR. This needs to be done before we can emit any
|
||||
// diagnostics, so their locations can be properly interpreted by our
|
||||
// diagnostics machinery.
|
||||
context.sem_ir().set_cpp_file(
|
||||
std::make_unique<SemIR::CppFile>(std::move(ast)));
|
||||
|
||||
// Emit any diagnostics we queued up while building the AST.
|
||||
context.emitter().Flush();
|
||||
|
||||
return !trap.hasErrorOccurred();
|
||||
}
|
||||
|
||||
// Adds a namespace for the `Cpp` import and returns its `NameScopeId`.
|
||||
static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
|
||||
llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
|
||||
@@ -468,11 +116,11 @@ auto ImportCpp(Context& context,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
|
||||
std::shared_ptr<clang::CompilerInvocation> invocation) -> void {
|
||||
if (imports.empty()) {
|
||||
// TODO: Consider always having a (non-null) AST even if there are no Cpp
|
||||
// imports.
|
||||
return;
|
||||
}
|
||||
|
||||
CARBON_CHECK(!context.sem_ir().cpp_file());
|
||||
|
||||
PackageNameId package_id = imports.front().package_id;
|
||||
CARBON_CHECK(
|
||||
llvm::all_of(imports, [&](const Parse::Tree::PackagingNames& import) {
|
||||
@@ -2508,7 +2156,7 @@ auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
|
||||
// Ask Clang whether the type is complete. This triggers template
|
||||
// instantiation if necessary.
|
||||
clang::DiagnosticErrorTrap trap(cpp_file->diagnostics());
|
||||
if (!cpp_file->sema().isCompleteType(
|
||||
if (!context.cpp_context()->sema().isCompleteType(
|
||||
loc, context.ast_context().getCanonicalTagType(clang_decl))) {
|
||||
// Type is incomplete. Nothing more to do, but tell the caller if we
|
||||
// produced an error.
|
||||
|
||||
Reference in New Issue
Block a user