Skip to content

Commit 7cac771

Browse files
zygoloidjonmeow
andauthored
Support import Cpp inline "some code";. (#5904)
This adds support for importing C++ code directly from source rather than via a `#include`. --------- Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent 4685890 commit 7cac771

19 files changed

+492
-37
lines changed

toolchain/check/check.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,14 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
8282
const auto& [import_package_name, import_library_name] = import_key;
8383

8484
if (import_package_name == CppPackageName) {
85-
if (import_library_name.empty()) {
85+
if (!explicit_import_map) {
86+
// Don't diagnose the implicit import in `impl package Cpp`, because we'll
87+
// have diagnosed the use of `Cpp` in the declaration.
88+
return;
89+
}
90+
if (!import.library_id.has_value() && !import.inline_body_id.has_value()) {
8691
CARBON_DIAGNOSTIC(CppInteropMissingLibrary, Error,
87-
"`Cpp` import missing library");
92+
"`Cpp` import without `library` or `inline`");
8893
unit_info.emitter.Emit(import.node_id, CppInteropMissingLibrary);
8994
return;
9095
}
@@ -95,7 +100,12 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
95100
unit_info.emitter.Emit(import.node_id, CppInteropFuzzing);
96101
return;
97102
}
98-
unit_info.cpp_import_names.push_back(import);
103+
unit_info.cpp_imports.push_back(import);
104+
return;
105+
} else if (import.inline_body_id.has_value()) {
106+
CARBON_DIAGNOSTIC(InlineImportNotCpp, Error,
107+
"`inline` import not in package `Cpp`");
108+
unit_info.emitter.Emit(import.node_id, InlineImportNotCpp);
99109
return;
100110
}
101111

@@ -216,11 +226,6 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
216226
} else {
217227
// The imported api is missing.
218228
package_imports.has_load_error = true;
219-
if (!explicit_import_map && import_package_name == CppPackageName) {
220-
// Don't diagnose the implicit import in `impl package Cpp`, because we'll
221-
// have diagnosed the use of `Cpp` in the declaration.
222-
return;
223-
}
224229
CARBON_DIAGNOSTIC(LibraryApiNotFound, Error,
225230
"corresponding API for '{0}' not found", std::string);
226231
CARBON_DIAGNOSTIC(ImportNotFound, Error, "imported API '{0}' not found",

toolchain/check/check_unit.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
4747
// Leave an empty slot for `ImportIRId::ApiForImpl`.
4848
++count;
4949
}
50-
if (!unit_and_imports->cpp_import_names.empty()) {
50+
if (!unit_and_imports->cpp_imports.empty()) {
5151
// Leave an empty slot for `ImportIRId::Cpp`.
5252
++count;
5353
}
@@ -152,13 +152,12 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
152152
CARBON_CHECK(context_.scope_stack().PeekIndex() == ScopeIndex::Package);
153153
ImportOtherPackages(namespace_type_id);
154154

155-
const auto& cpp_import_names = unit_and_imports_->cpp_import_names;
156-
if (!cpp_import_names.empty()) {
155+
const auto& cpp_imports = unit_and_imports_->cpp_imports;
156+
if (!cpp_imports.empty()) {
157157
auto* cpp_ast = unit_and_imports_->unit->cpp_ast;
158158
CARBON_CHECK(cpp_ast);
159159
CARBON_CHECK(!cpp_ast->get());
160-
*cpp_ast =
161-
ImportCppFiles(context_, cpp_import_names, fs_, clang_invocation_);
160+
*cpp_ast = ImportCppFiles(context_, cpp_imports, fs_, clang_invocation_);
162161
}
163162
}
164163

toolchain/check/check_unit.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ struct UnitAndImports {
9696
Map<PackageNameId, int32_t> package_imports_map;
9797

9898
// List of the `import Cpp` imports.
99-
llvm::SmallVector<Parse::Tree::PackagingNames> cpp_import_names;
99+
llvm::SmallVector<Parse::Tree::PackagingNames> cpp_imports;
100100

101101
// The remaining number of imports which must be checked before this unit can
102102
// be processed.

toolchain/check/handle_import_and_package.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,14 @@ auto HandleParseNode(Context& context, Parse::DefaultLibraryId node_id)
8989
return true;
9090
}
9191

92+
auto HandleParseNode(Context& /*context*/,
93+
Parse::InlineImportSpecifierId /*node_id*/) -> bool {
94+
return true;
95+
}
96+
97+
auto HandleParseNode(Context& /*context*/,
98+
Parse::InlineImportBodyId /*node_id*/) -> bool {
99+
return true;
100+
}
101+
92102
} // namespace Carbon::Check

toolchain/check/import_cpp.cpp

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,53 @@
5050

5151
namespace Carbon::Check {
5252

53+
// Add a line marker directive pointing at the location of the `import Cpp`
54+
// declaration in the Carbon source file. This will cause Clang's diagnostics
55+
// machinery to track and report the location in Carbon code where the import
56+
// was written.
57+
static auto GenerateLineMarker(Context& context, llvm::raw_ostream& out,
58+
int line) {
59+
out << "# " << line << " \""
60+
<< FormatEscaped(context.tokens().source().filename()) << "\"\n";
61+
}
62+
5363
// Generates C++ file contents to #include all requested imports.
5464
static auto GenerateCppIncludesHeaderCode(
5565
Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
5666
-> std::string {
5767
std::string code;
5868
llvm::raw_string_ostream code_stream(code);
5969
for (const Parse::Tree::PackagingNames& import : imports) {
60-
// Add a line marker directive pointing at the location of the `import Cpp`
61-
// declaration in the Carbon source file. This will cause Clang's
62-
// diagnostics machinery to track and report the location in Carbon code
63-
// where the import was written.
64-
auto token = context.parse_tree().node_token(import.node_id);
65-
code_stream << "# " << context.tokens().GetLineNumber(token) << " \""
66-
<< FormatEscaped(context.tokens().source().filename())
67-
<< "\"\n";
68-
69-
code_stream << "#include \""
70-
<< FormatEscaped(
71-
context.string_literal_values().Get(import.library_id))
72-
<< "\"\n";
70+
if (import.inline_body_id.has_value()) {
71+
// Expand `import Cpp inline "code";` directly into the specified code.
72+
auto code_token = context.parse_tree().node_token(import.inline_body_id);
73+
74+
// Compute the line number on which the C++ code starts. Usually the code
75+
// is specified as a block string literal and starts on the line after the
76+
// start of the string token.
77+
// TODO: Determine if this is a block string literal without calling
78+
// `GetTokenText`, which re-lexes the string.
79+
int line = context.tokens().GetLineNumber(code_token);
80+
if (context.tokens().GetTokenText(code_token).contains('\n')) {
81+
++line;
82+
}
83+
84+
GenerateLineMarker(context, code_stream, line);
85+
code_stream << context.string_literal_values().Get(
86+
context.tokens().GetStringLiteralValue(code_token))
87+
<< "\n";
88+
// TODO: Inject a clang pragma here to produce an error if there are
89+
// unclosed scopes at the end of this inline C++ fragment.
90+
} else {
91+
// Translate `import Cpp library "foo.h";` into `#include "foo.h"`.
92+
GenerateLineMarker(context, code_stream,
93+
context.tokens().GetLineNumber(
94+
context.parse_tree().node_token(import.node_id)));
95+
code_stream << "#include \""
96+
<< FormatEscaped(
97+
context.string_literal_values().Get(import.library_id))
98+
<< "\"\n";
99+
}
73100
}
74101
return code;
75102
}

toolchain/check/node_stack.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,8 @@ class NodeStack {
497497
case Parse::NodeKind::KeywordNameQualifierWithoutParams:
498498
case Parse::NodeKind::LibraryIntroducer:
499499
case Parse::NodeKind::LibrarySpecifier:
500+
case Parse::NodeKind::InlineImportSpecifier:
501+
case Parse::NodeKind::InlineImportBody:
500502
case Parse::NodeKind::MatchCase:
501503
case Parse::NodeKind::MatchCaseEqualGreater:
502504
case Parse::NodeKind::MatchCaseGuard:

toolchain/check/testdata/interop/cpp/bad_import.carbon

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
library "[[@TEST_NAME]]";
1616

17-
// CHECK:STDERR: fail_import_cpp.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
17+
// CHECK:STDERR: fail_import_cpp.carbon:[[@LINE+4]]:1: error: `Cpp` import without `library` or `inline` [CppInteropMissingLibrary]
1818
// CHECK:STDERR: import Cpp;
1919
// CHECK:STDERR: ^~~~~~~~~~~
2020
// CHECK:STDERR:
@@ -24,9 +24,9 @@ import Cpp;
2424

2525
library "[[@TEST_NAME]]";
2626

27-
// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
28-
// CHECK:STDERR: import Cpp library "";
29-
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~
27+
// CHECK:STDERR: fail_import_cpp_library_empty.carbon:[[@LINE+4]]:10: error: empty filename [CppInteropParseError]
28+
// CHECK:STDERR: 8 | #include ""
29+
// CHECK:STDERR: | ^
3030
// CHECK:STDERR:
3131
import Cpp library "";
3232

@@ -39,3 +39,16 @@ library "[[@TEST_NAME]]";
3939
// CHECK:STDERR: | ^~~~~~~~~~~
4040
// CHECK:STDERR:
4141
import Cpp library "\"foo.h\"";
42+
43+
// --- todo_fail_unterminated_import_inline.carbon
44+
45+
import Cpp inline '''c++
46+
void f() {
47+
''';
48+
49+
// TODO: We should diagnose that the inline C++ code didn't leave us at the top
50+
// level.
51+
52+
import Cpp inline '''c++
53+
}
54+
''';

toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,35 @@ library "[[@TEST_NAME]]";
410410
// CHECK:STDERR:
411411
import Cpp library "direct_include.h";
412412

413+
// ============================================================================
414+
// Diagnostic location in inline code.
415+
// ============================================================================
416+
417+
// --- fail_loc_in_inline_simple.carbon
418+
419+
library "[[@TEST_NAME]]";
420+
421+
// CHECK:STDERR: fail_loc_in_inline_simple.carbon:[[@LINE+4]]:9: error: use of undeclared identifier 'banana' [CppInteropParseError]
422+
// CHECK:STDERR: 8 | int n = banana;
423+
// CHECK:STDERR: | ^~~~~~
424+
// CHECK:STDERR:
425+
import Cpp inline "int n = banana;";
426+
427+
// --- fail_loc_in_inline_block.carbon
428+
429+
import Cpp inline '''c++
430+
void f(const int n) {
431+
// CHECK:STDERR: fail_loc_in_inline_block.carbon:[[@LINE+7]]:3: error: cannot assign to variable 'n' with const-qualified type 'const int' [CppInteropParseError]
432+
// CHECK:STDERR: 11 | ++n;
433+
// CHECK:STDERR: | ^ ~
434+
// CHECK:STDERR: fail_loc_in_inline_block.carbon:[[@LINE-4]]:18: note: variable 'n' declared const here [CppInteropParseNote]
435+
// CHECK:STDERR: 3 | void f(const int n) {
436+
// CHECK:STDERR: | ~~~~~~~~~~^
437+
// CHECK:STDERR:
438+
++n;
439+
}
440+
''';
441+
413442
// CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
414443
// CHECK:STDOUT:
415444
// CHECK:STDOUT: imports {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
6+
//
7+
// AUTOUPDATE
8+
// TIP: To test this file alone, run:
9+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/inline.carbon
10+
// TIP: To dump output, run:
11+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/inline.carbon
12+
13+
// --- use_inline_function_decl.carbon
14+
15+
library "[[@TEST_NAME]]";
16+
17+
import Cpp inline '''
18+
19+
// A C++ function.
20+
inline void func() {}
21+
22+
''';
23+
24+
fn Run() {
25+
//@dump-sem-ir-begin
26+
Cpp.func();
27+
//@dump-sem-ir-end
28+
}
29+
30+
// --- with_language_marker.carbon
31+
32+
library "[[@TEST_NAME]]";
33+
34+
import Cpp inline '''c++
35+
36+
// A C++ function.
37+
inline void another_func() {}
38+
39+
''';
40+
41+
fn Run() {
42+
//@dump-sem-ir-begin
43+
Cpp.another_func();
44+
//@dump-sem-ir-end
45+
}
46+
47+
// CHECK:STDOUT: --- use_inline_function_decl.carbon
48+
// CHECK:STDOUT:
49+
// CHECK:STDOUT: constants {
50+
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
51+
// CHECK:STDOUT: %func.type: type = fn_type @func [concrete]
52+
// CHECK:STDOUT: %func: %func.type = struct_value () [concrete]
53+
// CHECK:STDOUT: }
54+
// CHECK:STDOUT:
55+
// CHECK:STDOUT: imports {
56+
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
57+
// CHECK:STDOUT: .func = %func.decl
58+
// CHECK:STDOUT: import Cpp//...
59+
// CHECK:STDOUT: }
60+
// CHECK:STDOUT: %func.decl: %func.type = fn_decl @func [concrete = constants.%func] {} {}
61+
// CHECK:STDOUT: }
62+
// CHECK:STDOUT:
63+
// CHECK:STDOUT: fn @Run() {
64+
// CHECK:STDOUT: !entry:
65+
// CHECK:STDOUT: %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
66+
// CHECK:STDOUT: %func.ref: %func.type = name_ref func, imports.%func.decl [concrete = constants.%func]
67+
// CHECK:STDOUT: %func.call: init %empty_tuple.type = call %func.ref()
68+
// CHECK:STDOUT: <elided>
69+
// CHECK:STDOUT: }
70+
// CHECK:STDOUT:
71+
// CHECK:STDOUT: --- with_language_marker.carbon
72+
// CHECK:STDOUT:
73+
// CHECK:STDOUT: constants {
74+
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
75+
// CHECK:STDOUT: %another_func.type: type = fn_type @another_func [concrete]
76+
// CHECK:STDOUT: %another_func: %another_func.type = struct_value () [concrete]
77+
// CHECK:STDOUT: }
78+
// CHECK:STDOUT:
79+
// CHECK:STDOUT: imports {
80+
// CHECK:STDOUT: %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
81+
// CHECK:STDOUT: .another_func = %another_func.decl
82+
// CHECK:STDOUT: import Cpp//...
83+
// CHECK:STDOUT: }
84+
// CHECK:STDOUT: %another_func.decl: %another_func.type = fn_decl @another_func [concrete = constants.%another_func] {} {}
85+
// CHECK:STDOUT: }
86+
// CHECK:STDOUT:
87+
// CHECK:STDOUT: fn @Run() {
88+
// CHECK:STDOUT: !entry:
89+
// CHECK:STDOUT: %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
90+
// CHECK:STDOUT: %another_func.ref: %another_func.type = name_ref another_func, imports.%another_func.decl [concrete = constants.%another_func]
91+
// CHECK:STDOUT: %another_func.call: init %empty_tuple.type = call %another_func.ref()
92+
// CHECK:STDOUT: <elided>
93+
// CHECK:STDOUT: }
94+
// CHECK:STDOUT:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/none.carbon
6+
//
7+
// AUTOUPDATE
8+
// TIP: To test this file alone, run:
9+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon
10+
// TIP: To dump output, run:
11+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon
12+
13+
// CHECK:STDERR: fail_import_inline_not_cpp.carbon:[[@LINE+4]]:1: error: `inline` import not in package `Cpp` [InlineImportNotCpp]
14+
// CHECK:STDERR: import NotCpp inline "not C++ code";
15+
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
// CHECK:STDERR:
17+
import NotCpp inline "not C++ code";

0 commit comments

Comments
 (0)