瀏覽代碼

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 <jperkins@google.com>
Richard Smith 9 月之前
父節點
當前提交
7cac77119c

+ 13 - 8
toolchain/check/check.cpp

@@ -82,9 +82,14 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
   const auto& [import_package_name, import_library_name] = import_key;
 
   if (import_package_name == CppPackageName) {
-    if (import_library_name.empty()) {
+    if (!explicit_import_map) {
+      // Don't diagnose the implicit import in `impl package Cpp`, because we'll
+      // have diagnosed the use of `Cpp` in the declaration.
+      return;
+    }
+    if (!import.library_id.has_value() && !import.inline_body_id.has_value()) {
       CARBON_DIAGNOSTIC(CppInteropMissingLibrary, Error,
-                        "`Cpp` import missing library");
+                        "`Cpp` import without `library` or `inline`");
       unit_info.emitter.Emit(import.node_id, CppInteropMissingLibrary);
       return;
     }
@@ -95,7 +100,12 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
       unit_info.emitter.Emit(import.node_id, CppInteropFuzzing);
       return;
     }
-    unit_info.cpp_import_names.push_back(import);
+    unit_info.cpp_imports.push_back(import);
+    return;
+  } else if (import.inline_body_id.has_value()) {
+    CARBON_DIAGNOSTIC(InlineImportNotCpp, Error,
+                      "`inline` import not in package `Cpp`");
+    unit_info.emitter.Emit(import.node_id, InlineImportNotCpp);
     return;
   }
 
@@ -216,11 +226,6 @@ static auto TrackImport(Map<ImportKey, UnitAndImports*>& api_map,
   } else {
     // The imported api is missing.
     package_imports.has_load_error = true;
-    if (!explicit_import_map && import_package_name == CppPackageName) {
-      // Don't diagnose the implicit import in `impl package Cpp`, because we'll
-      // have diagnosed the use of `Cpp` in the declaration.
-      return;
-    }
     CARBON_DIAGNOSTIC(LibraryApiNotFound, Error,
                       "corresponding API for '{0}' not found", std::string);
     CARBON_DIAGNOSTIC(ImportNotFound, Error, "imported API '{0}' not found",

+ 4 - 5
toolchain/check/check_unit.cpp

@@ -47,7 +47,7 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int {
     // Leave an empty slot for `ImportIRId::ApiForImpl`.
     ++count;
   }
-  if (!unit_and_imports->cpp_import_names.empty()) {
+  if (!unit_and_imports->cpp_imports.empty()) {
     // Leave an empty slot for `ImportIRId::Cpp`.
     ++count;
   }
@@ -152,13 +152,12 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
   CARBON_CHECK(context_.scope_stack().PeekIndex() == ScopeIndex::Package);
   ImportOtherPackages(namespace_type_id);
 
-  const auto& cpp_import_names = unit_and_imports_->cpp_import_names;
-  if (!cpp_import_names.empty()) {
+  const auto& cpp_imports = unit_and_imports_->cpp_imports;
+  if (!cpp_imports.empty()) {
     auto* cpp_ast = unit_and_imports_->unit->cpp_ast;
     CARBON_CHECK(cpp_ast);
     CARBON_CHECK(!cpp_ast->get());
-    *cpp_ast =
-        ImportCppFiles(context_, cpp_import_names, fs_, clang_invocation_);
+    *cpp_ast = ImportCppFiles(context_, cpp_imports, fs_, clang_invocation_);
   }
 }
 

+ 1 - 1
toolchain/check/check_unit.h

@@ -96,7 +96,7 @@ struct UnitAndImports {
   Map<PackageNameId, int32_t> package_imports_map;
 
   // List of the `import Cpp` imports.
-  llvm::SmallVector<Parse::Tree::PackagingNames> cpp_import_names;
+  llvm::SmallVector<Parse::Tree::PackagingNames> cpp_imports;
 
   // The remaining number of imports which must be checked before this unit can
   // be processed.

+ 10 - 0
toolchain/check/handle_import_and_package.cpp

@@ -89,4 +89,14 @@ auto HandleParseNode(Context& context, Parse::DefaultLibraryId node_id)
   return true;
 }
 
+auto HandleParseNode(Context& /*context*/,
+                     Parse::InlineImportSpecifierId /*node_id*/) -> bool {
+  return true;
+}
+
+auto HandleParseNode(Context& /*context*/,
+                     Parse::InlineImportBodyId /*node_id*/) -> bool {
+  return true;
+}
+
 }  // namespace Carbon::Check

+ 40 - 13
toolchain/check/import_cpp.cpp

@@ -50,6 +50,16 @@
 
 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)
@@ -57,19 +67,36 @@ static auto GenerateCppIncludesHeaderCode(
   std::string code;
   llvm::raw_string_ostream code_stream(code);
   for (const Parse::Tree::PackagingNames& import : imports) {
-    // 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.
-    auto token = context.parse_tree().node_token(import.node_id);
-    code_stream << "# " << context.tokens().GetLineNumber(token) << " \""
-                << FormatEscaped(context.tokens().source().filename())
-                << "\"\n";
-
-    code_stream << "#include \""
-                << FormatEscaped(
-                       context.string_literal_values().Get(import.library_id))
-                << "\"\n";
+    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 {
+      // 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)));
+      code_stream << "#include \""
+                  << FormatEscaped(
+                         context.string_literal_values().Get(import.library_id))
+                  << "\"\n";
+    }
   }
   return code;
 }

+ 2 - 0
toolchain/check/node_stack.h

@@ -497,6 +497,8 @@ class NodeStack {
       case Parse::NodeKind::KeywordNameQualifierWithoutParams:
       case Parse::NodeKind::LibraryIntroducer:
       case Parse::NodeKind::LibrarySpecifier:
+      case Parse::NodeKind::InlineImportSpecifier:
+      case Parse::NodeKind::InlineImportBody:
       case Parse::NodeKind::MatchCase:
       case Parse::NodeKind::MatchCaseEqualGreater:
       case Parse::NodeKind::MatchCaseGuard:

+ 17 - 4
toolchain/check/testdata/interop/cpp/bad_import.carbon

@@ -14,7 +14,7 @@
 
 library "[[@TEST_NAME]]";
 
-// CHECK:STDERR: fail_import_cpp.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
+// CHECK:STDERR: fail_import_cpp.carbon:[[@LINE+4]]:1: error: `Cpp` import without `library` or `inline` [CppInteropMissingLibrary]
 // CHECK:STDERR: import Cpp;
 // CHECK:STDERR: ^~~~~~~~~~~
 // CHECK:STDERR:
@@ -24,9 +24,9 @@ import Cpp;
 
 library "[[@TEST_NAME]]";
 
-// 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: fail_import_cpp_library_empty.carbon:[[@LINE+4]]:10: error: empty filename [CppInteropParseError]
+// CHECK:STDERR:     8 | #include ""
+// CHECK:STDERR:       |          ^
 // CHECK:STDERR:
 import Cpp library "";
 
@@ -39,3 +39,16 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR:       |          ^~~~~~~~~~~
 // CHECK:STDERR:
 import Cpp library "\"foo.h\"";
+
+// --- todo_fail_unterminated_import_inline.carbon
+
+import Cpp inline '''c++
+void f() {
+''';
+
+// TODO: We should diagnose that the inline C++ code didn't leave us at the top
+// level.
+
+import Cpp inline '''c++
+}
+''';

+ 29 - 0
toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon

@@ -410,6 +410,35 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR:
 import Cpp library "direct_include.h";
 
+// ============================================================================
+// Diagnostic location in inline code.
+// ============================================================================
+
+// --- fail_loc_in_inline_simple.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_loc_in_inline_simple.carbon:[[@LINE+4]]:9: error: use of undeclared identifier 'banana' [CppInteropParseError]
+// CHECK:STDERR:     8 | int n = banana;
+// CHECK:STDERR:       |         ^~~~~~
+// CHECK:STDERR:
+import Cpp inline "int n = banana;";
+
+// --- fail_loc_in_inline_block.carbon
+
+import Cpp inline '''c++
+void f(const int n) {
+  // CHECK:STDERR: fail_loc_in_inline_block.carbon:[[@LINE+7]]:3: error: cannot assign to variable 'n' with const-qualified type 'const int' [CppInteropParseError]
+  // CHECK:STDERR:    11 |   ++n;
+  // CHECK:STDERR:       |   ^ ~
+  // CHECK:STDERR: fail_loc_in_inline_block.carbon:[[@LINE-4]]:18: note: variable 'n' declared const here [CppInteropParseNote]
+  // CHECK:STDERR:     3 | void f(const int n) {
+  // CHECK:STDERR:       |        ~~~~~~~~~~^
+  // CHECK:STDERR:
+  ++n;
+}
+''';
+
 // CHECK:STDOUT: --- import_cpp_file_with_one_warning.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: imports {

+ 94 - 0
toolchain/check/testdata/interop/cpp/inline.carbon

@@ -0,0 +1,94 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/inline.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/inline.carbon
+
+// --- use_inline_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''
+
+// A C++ function.
+inline void func() {}
+
+''';
+
+fn Run() {
+  //@dump-sem-ir-begin
+  Cpp.func();
+  //@dump-sem-ir-end
+}
+
+// --- with_language_marker.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+
+// A C++ function.
+inline void another_func() {}
+
+''';
+
+fn Run() {
+  //@dump-sem-ir-begin
+  Cpp.another_func();
+  //@dump-sem-ir-end
+}
+
+// CHECK:STDOUT: --- use_inline_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %func.type: type = fn_type @func [concrete]
+// CHECK:STDOUT:   %func: %func.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .func = %func.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %func.decl: %func.type = fn_decl @func [concrete = constants.%func] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %func.ref: %func.type = name_ref func, imports.%func.decl [concrete = constants.%func]
+// CHECK:STDOUT:   %func.call: init %empty_tuple.type = call %func.ref()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- with_language_marker.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %another_func.type: type = fn_type @another_func [concrete]
+// CHECK:STDOUT:   %another_func: %another_func.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .another_func = %another_func.decl
+// CHECK:STDOUT:     import Cpp//...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %another_func.decl: %another_func.type = fn_decl @another_func [concrete = constants.%another_func] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Run() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %another_func.ref: %another_func.type = name_ref another_func, imports.%another_func.decl [concrete = constants.%another_func]
+// CHECK:STDOUT:   %another_func.call: init %empty_tuple.type = call %another_func.ref()
+// CHECK:STDOUT:   <elided>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 17 - 0
toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon

@@ -0,0 +1,17 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/none.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon
+
+// CHECK:STDERR: fail_import_inline_not_cpp.carbon:[[@LINE+4]]:1: error: `inline` import not in package `Cpp` [InlineImportNotCpp]
+// CHECK:STDERR: import NotCpp inline "not C++ code";
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+import NotCpp inline "not C++ code";

+ 1 - 5
toolchain/check/testdata/packages/restricted_package_names.carbon

@@ -65,11 +65,7 @@ package Cpp;
 
 // --- fail_cpp.impl.carbon
 
-// CHECK:STDERR: fail_cpp.impl.carbon:[[@LINE+8]]:1: error: `Cpp` cannot be used by a `package` declaration [CppPackageDeclaration]
-// CHECK:STDERR: impl package Cpp;
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-// CHECK:STDERR: fail_cpp.impl.carbon:[[@LINE+4]]:1: error: `Cpp` import missing library [CppInteropMissingLibrary]
+// CHECK:STDERR: fail_cpp.impl.carbon:[[@LINE+4]]:1: error: `Cpp` cannot be used by a `package` declaration [CppPackageDeclaration]
 // CHECK:STDERR: impl package Cpp;
 // CHECK:STDERR: ^~~~~~~~~~~~~~~~~
 // CHECK:STDERR:

+ 2 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -130,6 +130,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterImport)
 CARBON_DIAGNOSTIC_KIND(ExpectedLibraryName)
 CARBON_DIAGNOSTIC_KIND(ExpectedLibraryNameOrDefault)
 CARBON_DIAGNOSTIC_KIND(MissingLibraryKeyword)
+CARBON_DIAGNOSTIC_KIND(ExpectedStringAfterInline)
 CARBON_DIAGNOSTIC_KIND(ExportImportPackage)
 CARBON_DIAGNOSTIC_KIND(ExpectedPeriodAfterPackage)
 
@@ -182,6 +183,7 @@ CARBON_DIAGNOSTIC_KIND(CppInteropDriverWarning)
 CARBON_DIAGNOSTIC_KIND(CppInteropParseError)
 CARBON_DIAGNOSTIC_KIND(CppInteropParseWarning)
 CARBON_DIAGNOSTIC_KIND(CppInteropParseNote)
+CARBON_DIAGNOSTIC_KIND(InlineImportNotCpp)
 CARBON_DIAGNOSTIC_KIND(IncorrectExtension)
 CARBON_DIAGNOSTIC_KIND(IncorrectExtensionImplNote)
 CARBON_DIAGNOSTIC_KIND(DuplicateLibraryApi)

+ 1 - 0
toolchain/lex/token_kind.def

@@ -191,6 +191,7 @@ CARBON_KEYWORD_TOKEN(Friend,              "friend")
 CARBON_KEYWORD_TOKEN(If,                  "if")
 CARBON_KEYWORD_TOKEN(Impls,               "impls")
 CARBON_KEYWORD_TOKEN(In,                  "in")
+CARBON_KEYWORD_TOKEN(Inline,              "inline")
 CARBON_KEYWORD_TOKEN(Like,                "like")
 CARBON_KEYWORD_TOKEN(Match,               "match")
 CARBON_KEYWORD_TOKEN(Not,                 "not")

+ 74 - 0
toolchain/lower/testdata/interop/cpp/import_inline.carbon

@@ -0,0 +1,74 @@
+// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/import_inline.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/import_inline.carbon
+
+import Cpp inline '''
+
+void F(int *_Nonnull p) {
+  *p = 1;
+}
+
+''';
+
+fn Call() -> i32 {
+  var n: i32 = 0;
+  Cpp.F(&n);
+  return n;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'import_inline.carbon'
+// CHECK:STDOUT: source_filename = "import_inline.carbon"
+// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i32 @_CCall.Main() !dbg !7 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %n.var = alloca i32, align 4, !dbg !10
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 4, ptr %n.var), !dbg !10
+// CHECK:STDOUT:   store i32 0, ptr %n.var, align 4, !dbg !10
+// CHECK:STDOUT:   call void @_Z1FPi(ptr %n.var), !dbg !11
+// CHECK:STDOUT:   %.loc24 = load i32, ptr %n.var, align 4, !dbg !12
+// CHECK:STDOUT:   ret i32 %.loc24, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
+// CHECK:STDOUT: define dso_local void @_Z1FPi(ptr %p) #1 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %p.addr = alloca ptr, align 8
+// CHECK:STDOUT:   store ptr %p, ptr %p.addr, align 8
+// CHECK:STDOUT:   %0 = load ptr, ptr %p.addr, align 8
+// CHECK:STDOUT:   store i32 1, ptr %0, align 4
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT: attributes #1 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
+// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
+// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
+// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !6 = !DIFile(filename: "import_inline.carbon", directory: "")
+// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "Call", linkageName: "_CCall.Main", scope: null, file: !6, line: 21, type: !8, spFlags: DISPFlagDefinition, unit: !5)
+// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
+// CHECK:STDOUT: !9 = !{}
+// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 3, scope: !7)
+// CHECK:STDOUT: !11 = !DILocation(line: 23, column: 3, scope: !7)
+// CHECK:STDOUT: !12 = !DILocation(line: 24, column: 10, scope: !7)
+// CHECK:STDOUT: !13 = !DILocation(line: 24, column: 3, scope: !7)

+ 17 - 0
toolchain/parse/handle_import_and_package.cpp

@@ -56,6 +56,7 @@ static auto HandleDeclContent(Context& context, Context::State state,
           PackageNameId::ForIdentifier(context.tokens().GetIdentifier(*ident));
       context.AddLeafNode(NodeKind::IdentifierPackageName, *ident);
     } else if (auto core = context.ConsumeIf(Lex::TokenKind::Core)) {
+      // TODO: Model `Cpp` as a keyword too.
       names.package_id = PackageNameId::Core;
       context.AddLeafNode(NodeKind::CorePackageName, *core);
     } else {
@@ -99,6 +100,22 @@ static auto HandleDeclContent(Context& context, Context::State state,
         on_parse_error();
         return;
       }
+    } else if (DeclKind == NodeKind::ImportDecl &&
+               next_kind == Lex::TokenKind::Inline) {
+      auto inline_token = context.ConsumeChecked(Lex::TokenKind::Inline);
+      auto body_position = *context.position();
+      auto inline_body_token = context.ConsumeIf(Lex::TokenKind::StringLiteral);
+      names.inline_body_id = context.AddNode<NodeKind::InlineImportBody>(
+          body_position, /*has_error=*/!inline_body_token);
+      context.AddNode(NodeKind::InlineImportSpecifier, inline_token,
+                      /*has_error=*/false);
+      if (!inline_body_token) {
+        CARBON_DIAGNOSTIC(ExpectedStringAfterInline, Error,
+                          "expected string literal after `inline`");
+        context.emitter().Emit(body_position, ExpectedStringAfterInline);
+        on_parse_error();
+        return;
+      }
     } else if (next_kind == Lex::TokenKind::StringLiteral ||
                (accept_default && next_kind == Lex::TokenKind::Default)) {
       // If we come across a string literal and we didn't parse `library

+ 3 - 0
toolchain/parse/node_kind.def

@@ -128,6 +128,9 @@ CARBON_PARSE_NODE_KIND(LibraryDecl)
 
 CARBON_PARSE_NODE_KIND(LibrarySpecifier)
 
+CARBON_PARSE_NODE_KIND(InlineImportSpecifier)
+CARBON_PARSE_NODE_KIND(InlineImportBody)
+
 CARBON_PARSE_NODE_KIND(IdentifierNameQualifierWithParams)
 CARBON_PARSE_NODE_KIND(IdentifierNameQualifierWithoutParams)
 CARBON_PARSE_NODE_KIND(KeywordNameQualifierWithParams)

+ 152 - 0
toolchain/parse/testdata/packages/import/cpp_inline.carbon

@@ -0,0 +1,152 @@
+// 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
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/packages/import/cpp_inline.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/packages/import/cpp_inline.carbon
+
+// --- fail_bad_syntax.carbon
+
+// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:18: error: expected string literal after `inline` [ExpectedStringAfterInline]
+// CHECK:STDERR: import Cpp inline;
+// CHECK:STDERR:                  ^
+// CHECK:STDERR:
+import Cpp inline;
+// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:19: error: expected string literal after `inline` [ExpectedStringAfterInline]
+// CHECK:STDERR: import Cpp inline library "foo.h";
+// CHECK:STDERR:                   ^~~~~~~
+// CHECK:STDERR:
+import Cpp inline library "foo.h";
+
+// TODO: We might want to allow this as a "preprocessed source" syntax.
+// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:28: error: `import` declarations must end with a `;` [ExpectedDeclSemi]
+// CHECK:STDERR: import Cpp library "foo.h" inline "bar";
+// CHECK:STDERR:                            ^~~~~~
+// CHECK:STDERR:
+import Cpp library "foo.h" inline "bar";
+
+// CHECK:STDERR: fail_bad_syntax.carbon:[[@LINE+4]]:24: error: `import` declarations must end with a `;` [ExpectedDeclSemi]
+// CHECK:STDERR: import library "foo.h" inline "bar";
+// CHECK:STDERR:                        ^~~~~~
+// CHECK:STDERR:
+import library "foo.h" inline "bar";
+
+// --- fail_no_semi_at_eof.carbon
+
+import Cpp inline
+// CHECK:STDERR: fail_no_semi_at_eof.carbon:[[@LINE+4]]:1: error: expected string literal after `inline` [ExpectedStringAfterInline]
+// CHECK:STDERR:
+// CHECK:STDERR: ^
+// CHECK:STDERR:
+
+// --- fail_package_inline.carbon
+
+// CHECK:STDERR: fail_package_inline.carbon:[[@LINE+4]]:16: error: `package` declarations must end with a `;` [ExpectedDeclSemi]
+// CHECK:STDERR: package NotCpp inline "int n;";
+// CHECK:STDERR:                ^~~~~~
+// CHECK:STDERR:
+package NotCpp inline "int n;";
+
+// --- fail_library_inline.carbon
+
+// CHECK:STDERR: fail_library_inline.carbon:[[@LINE+4]]:17: error: `library` declarations must end with a `;` [ExpectedDeclSemi]
+// CHECK:STDERR: library "foo.h" inline "int n;";
+// CHECK:STDERR:                 ^~~~~~
+// CHECK:STDERR:
+library "foo.h" inline "int n;";
+
+// --- wrong_package.carbon
+
+// We reject this in check.
+import NotCpp inline "bar";
+
+// --- valid_syntax.carbon
+
+import Cpp inline "int m;";
+
+import Cpp inline '''
+// C++ comment.
+int n;
+''';
+
+// CHECK:STDOUT: - filename: fail_bad_syntax.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: ';', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: 'library', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"foo.h"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:         {kind: 'LibraryName', text: '"foo.h"'},
+// CHECK:STDOUT:       {kind: 'LibrarySpecifier', text: 'library', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', has_error: yes, subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_no_semi_at_eof.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: '', has_error: yes},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: 'inline', has_error: yes, subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_package_inline.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'PackageIntroducer', text: 'package'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'NotCpp'},
+// CHECK:STDOUT:     {kind: 'PackageDecl', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_library_inline.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'LibraryIntroducer', text: 'library'},
+// CHECK:STDOUT:       {kind: 'LibraryName', text: '"foo.h"'},
+// CHECK:STDOUT:     {kind: 'LibraryDecl', text: ';', has_error: yes, subtree_size: 3},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: wrong_package.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'NotCpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: '"bar"'},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: valid_syntax.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: '"int m;"'},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ImportIntroducer', text: 'import'},
+// CHECK:STDOUT:       {kind: 'IdentifierPackageName', text: 'Cpp'},
+// CHECK:STDOUT:         {kind: 'InlineImportBody', text: ''''
+// CHECK:STDOUT: // C++ comment.
+// CHECK:STDOUT: int n;
+// CHECK:STDOUT: ''''},
+// CHECK:STDOUT:       {kind: 'InlineImportSpecifier', text: 'inline', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'ImportDecl', text: ';', subtree_size: 5},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 0
toolchain/parse/tree.h

@@ -80,6 +80,7 @@ class Tree : public Printable<Tree> {
     PackageNameId package_id = PackageNameId::None;
     // TODO: Move LibraryNameId to Base and use it here.
     StringLiteralValueId library_id = StringLiteralValueId::None;
+    InlineImportBodyId inline_body_id = InlineImportBodyId::None;
     // Whether an import is exported. This is on the file's packaging
     // declaration even though it doesn't apply, for consistency in structure.
     bool is_export = false;

+ 14 - 1
toolchain/parse/typed_nodes.h

@@ -258,6 +258,18 @@ struct LibrarySpecifier {
   NodeIdOneOf<LibraryName, DefaultLibrary> name;
 };
 
+using InlineImportBody =
+    LeafNode<NodeKind::InlineImportBody, Lex::StringLiteralTokenIndex>;
+
+// `inline` in `import`.
+struct InlineImportSpecifier {
+  static constexpr auto Kind =
+      NodeKind::InlineImportSpecifier.Define({.child_count = 1});
+
+  Lex::InlineTokenIndex token;
+  InlineImportBodyId body;
+};
+
 // First line of the file, such as:
 //   `impl package MyPackage library "MyLibrary";`
 struct PackageDecl {
@@ -272,7 +284,7 @@ struct PackageDecl {
   Lex::SemiTokenIndex token;
 };
 
-// `import TheirPackage library "TheirLibrary";`
+// `import [TheirPackage] [library "TheirLibrary" | inline "code"];`
 using ImportIntroducer =
     LeafNode<NodeKind::ImportIntroducer, Lex::ImportTokenIndex>;
 struct ImportDecl {
@@ -283,6 +295,7 @@ struct ImportDecl {
   llvm::SmallVector<AnyModifierId> modifiers;
   std::optional<AnyPackageNameId> name;
   std::optional<LibrarySpecifierId> library;
+  std::optional<InlineImportSpecifierId> inline_specifier;
   Lex::SemiTokenIndex token;
 };