Skip to content

Commit

Permalink
Support multiple import Cpp library in a single unit
Browse files Browse the repository at this point in the history
Instead of compiling the imported file, generate a C++ header that includes all `Cpp` import files.
Part of #4666
  • Loading branch information
bricknerb committed Jan 16, 2025
1 parent e348119 commit 71434eb
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 60 deletions.
27 changes: 9 additions & 18 deletions toolchain/check/check_unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,26 +340,17 @@ auto CheckUnit::ImportCppPackages() -> void {
return;
}

if (imports.size() >= 2) {
context_.TODO(imports[1].node_id,
"multiple Cpp imports are not yet supported");
return;
}

const auto& import = imports.front();
llvm::StringRef filename =
unit_and_imports_->unit->value_stores->string_literal_values().Get(
import.library_id);

// TODO: Pass the import location so that diagnostics would point to it.
auto source_buffer = SourceBuffer::MakeFromFile(
*fs_, filename, unit_and_imports_->err_tracker);
if (!source_buffer) {
return;
llvm::SmallVector<std::pair<llvm::StringRef, SemIRLoc>> import_pairs;
import_pairs.reserve(imports.size());
for (const auto& import : imports) {
import_pairs.push_back(
{unit_and_imports_->unit->value_stores->string_literal_values().Get(
import.library_id),
import.node_id});
}

ImportCppFile(context_, import.node_id, fs_, source_buffer->filename(),
source_buffer->text());
ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
import_pairs, fs_);
}

// Loops over all nodes in the tree. On some errors, this may return early,
Expand Down
55 changes: 45 additions & 10 deletions toolchain/check/import_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,55 @@
#include "toolchain/diagnostics/format_providers.h"

namespace Carbon::Check {
namespace {

auto GenerateCppIncludesHeaderFilename(llvm::StringRef importing_file_path)
-> std::string {
return importing_file_path.str() + ".generated.cpp_imports.h";
}

auto GenerateCppIncludesHeaderCode(
llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports)
-> std::string {
constexpr llvm::StringLiteral include_prefix(R"(#include ")");
constexpr llvm::StringLiteral include_suffix(R"("
)");

std::string code;
for (const auto& [path, _] : imports) {
code.append(
llvm::Twine(include_prefix).concat(path).concat(include_suffix).str());
}
return code;
}

} // namespace

auto ImportCppFiles(
Context& context, llvm::StringRef importing_file_path,
llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void {
size_t num_imports = imports.size();
if (num_imports == 0) {
return;
}

// TODO: Use all import locations by referring each Clang diagnostic to the
// relevant import.
SemIRLoc loc = imports.back().second;

auto ImportCppFile(Context& context, SemIRLoc loc,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::StringRef file_path, llvm::StringRef code) -> void {
std::string diagnostics_str;
llvm::raw_string_ostream diagnostics_stream(diagnostics_str);

llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> diagnostic_options(
new clang::DiagnosticOptions());
clang::TextDiagnosticPrinter diagnostics_consumer(diagnostics_stream,
diagnostic_options.get());

// TODO: Share compilation flags with ClangRunner.
auto ast = clang::tooling::buildASTFromCodeWithArgs(
code, {}, file_path, "clang-tool",
GenerateCppIncludesHeaderCode(imports), {},
GenerateCppIncludesHeaderFilename(importing_file_path), "clang-tool",
std::make_shared<clang::PCHContainerOperations>(),
clang::tooling::getClangStripDependencyFileAdjuster(),
clang::tooling::FileContentMappings(), &diagnostics_consumer, fs);
Expand All @@ -42,16 +77,16 @@ auto ImportCppFile(Context& context, SemIRLoc loc,
// TODO: Remove the warnings part when there are no warnings.
CARBON_DIAGNOSTIC(
CppInteropParseError, Error,
"{0} error{0:s} and {1} warning{1:s} in `Cpp` import `{2}`:\n{3}",
IntAsSelect, IntAsSelect, std::string, std::string);
"{0} error{0:s} and {1} warning{1:s} in {2} `Cpp` import{2:s}:\n{3}",
IntAsSelect, IntAsSelect, IntAsSelect, std::string);
context.emitter().Emit(loc, CppInteropParseError, num_errors, num_warnings,
file_path.str(), diagnostics_str);
num_imports, diagnostics_str);
} else if (num_warnings > 0) {
CARBON_DIAGNOSTIC(CppInteropParseWarning, Warning,
"{0} warning{0:s} in `Cpp` import `{1}`:\n{2}",
IntAsSelect, std::string, std::string);
"{0} warning{0:s} in `Cpp` {1} import{1:s}:\n{2}",
IntAsSelect, IntAsSelect, std::string);
context.emitter().Emit(loc, CppInteropParseWarning, num_warnings,
file_path.str(), diagnostics_str);
num_imports, diagnostics_str);
}
}

Expand Down
10 changes: 6 additions & 4 deletions toolchain/check/import_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@

namespace Carbon::Check {

// Parses the C++ code and report errors and warnings.
auto ImportCppFile(Context& context, SemIRLoc loc,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
llvm::StringRef file_path, llvm::StringRef code) -> void;
// Generates a C++ header that includes the imported cpp files, parses it and
// report errors and warnings.
auto ImportCppFiles(
Context& context, llvm::StringRef importing_file_path,
llvm::ArrayRef<std::pair<llvm::StringRef, SemIRLoc>> imports,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void;

} // namespace Carbon::Check

Expand Down
14 changes: 12 additions & 2 deletions toolchain/check/testdata/interop/cpp/no_prelude/bad_import.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,27 @@ import Cpp;

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+5]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
// CHECK:STDERR: import Cpp library "";
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR:
// CHECK:STDERR: "foo.h": error: error opening file for read: No such file or directory [ErrorOpeningFile]
import Cpp library "";

// --- fail_import_cpp_library_file_with_quotes.carbon

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon:[[@LINE+11]]:1: error: 1 error and 1 warning in 1 `Cpp` import:
// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon.generated.cpp_imports.h:1:12: warning: extra tokens at end of #include directive
// CHECK:STDERR: 1 | #include ""foo.h""
// CHECK:STDERR: | ^
// CHECK:STDERR: | //
// CHECK:STDERR: fail_import_cpp_library_file_with_quotes.carbon.generated.cpp_imports.h:1:10: error: empty filename
// CHECK:STDERR: 1 | #include ""foo.h""
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
// CHECK:STDERR: import Cpp library "\"foo.h\"";
// CHECK:STDERR: ^~~~~~
import Cpp library "\"foo.h\"";

// CHECK:STDOUT: --- fail_import_cpp.carbon
Expand Down
119 changes: 99 additions & 20 deletions toolchain/check/testdata/interop/cpp/no_prelude/cpp_diagnostics.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_file_with_one_error.carbon:[[@LINE+8]]:1: error: 1 error and 0 warnings in `Cpp` import `one_error.h`:
// CHECK:STDERR: one_error.h:2:2: error: "error1"
// CHECK:STDERR: fail_import_cpp_file_with_one_error.carbon:[[@LINE+9]]:1: error: 1 error and 0 warnings in 1 `Cpp` import:
// CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./one_error.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
Expand All @@ -35,11 +36,12 @@ import Cpp library "one_error.h";

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+11]]:1: error: 2 errors and 0 warnings in `Cpp` import `multiple_errors.h`:
// CHECK:STDERR: multiple_errors.h:2:2: error: "error1"
// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors.carbon:[[@LINE+12]]:1: error: 2 errors and 0 warnings in 1 `Cpp` import:
// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./multiple_errors.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_errors.h:3:2: error: "error2"
// CHECK:STDERR: ./multiple_errors.h:3:2: error: "error2"
// CHECK:STDERR: 3 | #error "error2"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
Expand All @@ -56,8 +58,9 @@ import Cpp library "multiple_errors.h";

library "[[@TEST_NAME]]";

// CHECK:STDERR: import_cpp_file_with_one_warning.carbon:[[@LINE+8]]:1: warning: 1 warning in `Cpp` import `one_warning.h`:
// CHECK:STDERR: one_warning.h:2:2: warning: "warning1"
// CHECK:STDERR: import_cpp_file_with_one_warning.carbon:[[@LINE+9]]:1: warning: 1 warning in `Cpp` 1 import:
// CHECK:STDERR: In file included from import_cpp_file_with_one_warning.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
// CHECK:STDERR: 2 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseWarning]
Expand All @@ -76,14 +79,15 @@ import Cpp library "one_warning.h";

library "[[@TEST_NAME]]";

// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+14]]:1: warning: 3 warnings in `Cpp` import `multiple_warnings.h`:
// CHECK:STDERR: multiple_warnings.h:2:2: warning: "warning1"
// CHECK:STDERR: import_cpp_file_with_multiple_warnings.carbon:[[@LINE+15]]:1: warning: 3 warnings in `Cpp` 1 import:
// CHECK:STDERR: In file included from import_cpp_file_with_multiple_warnings.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
// CHECK:STDERR: 2 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_warnings.h:3:2: warning: "warning2"
// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
// CHECK:STDERR: 3 | #warning "warning2"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_warnings.h:4:2: warning: "warning3"
// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
// CHECK:STDERR: 4 | #warning "warning3"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseWarning]
Expand All @@ -101,11 +105,12 @@ import Cpp library "multiple_warnings.h";

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+11]]:1: error: 1 error and 1 warning in `Cpp` import `one_error_and_one_warning.h`:
// CHECK:STDERR: one_error_and_one_warning.h:2:2: error: "error1"
// CHECK:STDERR: fail_import_cpp_file_with_one_error_and_one_warning.carbon:[[@LINE+12]]:1: error: 1 error and 1 warning in 1 `Cpp` import:
// CHECK:STDERR: In file included from fail_import_cpp_file_with_one_error_and_one_warning.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: one_error_and_one_warning.h:3:2: warning: "warning1"
// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
// CHECK:STDERR: 3 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
Expand All @@ -126,20 +131,82 @@ import Cpp library "one_error_and_one_warning.h";

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+19]]:1: error: 2 errors and 3 warnings in `Cpp` import `multiple_errors_and_multiple_warnings.h`:
// CHECK:STDERR: multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
// CHECK:STDERR: fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon:[[@LINE+21]]:1: error: 2 errors and 3 warnings in 1 `Cpp` import:
// CHECK:STDERR: In file included from fail_import_cpp_file_with_multiple_errors_and_multiple_warnings.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
// CHECK:STDERR: 3 | #error "error2"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
// CHECK:STDERR: 4 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
// CHECK:STDERR: 5 | #warning "warning2"
// CHECK:STDERR: | ^
// CHECK:STDERR: multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
// CHECK:STDERR: 6 | #warning "warning3"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
// CHECK:STDERR: import Cpp library "multiple_errors_and_multiple_warnings.h";
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR:
import Cpp library "multiple_errors_and_multiple_warnings.h";

// --- import_multiple_cpp_files_with_warnings.carbon

library "[[@TEST_NAME]]";

import Cpp library "one_warning.h";
// CHECK:STDERR: import_multiple_cpp_files_with_warnings.carbon:[[@LINE+19]]:1: warning: 4 warnings in `Cpp` 2 imports:
// CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./one_warning.h:2:2: warning: "warning1"
// CHECK:STDERR: 2 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: In file included from import_multiple_cpp_files_with_warnings.carbon.generated.cpp_imports.h:2:
// CHECK:STDERR: ./multiple_warnings.h:2:2: warning: "warning1"
// CHECK:STDERR: 2 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_warnings.h:3:2: warning: "warning2"
// CHECK:STDERR: 3 | #warning "warning2"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_warnings.h:4:2: warning: "warning3"
// CHECK:STDERR: 4 | #warning "warning3"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseWarning]
// CHECK:STDERR: import Cpp library "multiple_warnings.h";
// CHECK:STDERR: ^~~~~~
// CHECK:STDERR:
import Cpp library "multiple_warnings.h";

// --- fail_import_multiple_cpp_files_with_warnings_and_errors.carbon

library "[[@TEST_NAME]]";

import Cpp library "one_error_and_one_warning.h";
// CHECK:STDERR: fail_import_multiple_cpp_files_with_warnings_and_errors.carbon:[[@LINE+27]]:1: error: 3 errors and 4 warnings in 2 `Cpp` imports:
// CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:1:
// CHECK:STDERR: ./one_error_and_one_warning.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./one_error_and_one_warning.h:3:2: warning: "warning1"
// CHECK:STDERR: 3 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: In file included from fail_import_multiple_cpp_files_with_warnings_and_errors.carbon.generated.cpp_imports.h:2:
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:2:2: error: "error1"
// CHECK:STDERR: 2 | #error "error1"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:3:2: error: "error2"
// CHECK:STDERR: 3 | #error "error2"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:4:2: warning: "warning1"
// CHECK:STDERR: 4 | #warning "warning1"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:5:2: warning: "warning2"
// CHECK:STDERR: 5 | #warning "warning2"
// CHECK:STDERR: | ^
// CHECK:STDERR: ./multiple_errors_and_multiple_warnings.h:6:2: warning: "warning3"
// CHECK:STDERR: 6 | #warning "warning3"
// CHECK:STDERR: | ^
// CHECK:STDERR: [CppInteropParseError]
Expand Down Expand Up @@ -183,3 +250,15 @@ import Cpp library "multiple_errors_and_multiple_warnings.h";
// CHECK:STDOUT: package: <namespace> = namespace [template] {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- import_multiple_cpp_files_with_warnings.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_import_multiple_cpp_files_with_warnings_and_errors.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/no_prelude/file_not_found.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/no_prelude/file_not_found.carbon
// CHECK:STDERR: not_found.h: error: error opening file for read: No such file or directory [ErrorOpeningFile]

// --- fail_cpp_file_not_found.carbon

library "[[@TEST_NAME]]";

// CHECK:STDERR: fail_cpp_file_not_found.carbon:[[@LINE+7]]:1: error: 1 error and 0 warnings in 1 `Cpp` import:
// CHECK:STDERR: fail_cpp_file_not_found.carbon.generated.cpp_imports.h:1:10: fatal error: 'not_found.h' file not found
// CHECK:STDERR: 1 | #include "not_found.h"
// CHECK:STDERR: | ^~~~~~~~~~~~~
// CHECK:STDERR: [CppInteropParseError]
// CHECK:STDERR: import Cpp library "not_found.h";
// CHECK:STDERR: ^~~~~~
import Cpp library "not_found.h";

// CHECK:STDOUT: --- fail_cpp_file_not_found.carbon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@ void foo1();

void foo1();

// --- fail_multiple_imports.carbon
// --- multiple_imports.carbon

library "[[@TEST_NAME]]";

import Cpp library "file1.h";
// CHECK:STDERR: fail_multiple_imports.carbon:[[@LINE+3]]:1: error: semantics TODO: `multiple Cpp imports are not yet supported` [SemanticsTodo]
// CHECK:STDERR: import Cpp library "file2.h";
// CHECK:STDERR: ^~~~~~
import Cpp library "file2.h";

// CHECK:STDOUT: --- fail_multiple_imports.carbon
// CHECK:STDOUT: --- multiple_imports.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {}
Expand Down

0 comments on commit 71434eb

Please sign in to comment.