diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index 3bfda9321b09..0c8e07a7b161 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -82,9 +82,14 @@ static auto TrackImport(Map& 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& 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& 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", diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index b09b72b47be8..d1ede66d5a3d 100644 --- a/toolchain/check/check_unit.cpp +++ b/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_); } } diff --git a/toolchain/check/check_unit.h b/toolchain/check/check_unit.h index a3622fbfae57..d4e75499f98a 100644 --- a/toolchain/check/check_unit.h +++ b/toolchain/check/check_unit.h @@ -96,7 +96,7 @@ struct UnitAndImports { Map package_imports_map; // List of the `import Cpp` imports. - llvm::SmallVector cpp_import_names; + llvm::SmallVector cpp_imports; // The remaining number of imports which must be checked before this unit can // be processed. diff --git a/toolchain/check/handle_import_and_package.cpp b/toolchain/check/handle_import_and_package.cpp index 98f55918c2d1..71e6bbcc02ee 100644 --- a/toolchain/check/handle_import_and_package.cpp +++ b/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 diff --git a/toolchain/check/import_cpp.cpp b/toolchain/check/import_cpp.cpp index 9637b306dbe1..d0cc1662a3e9 100644 --- a/toolchain/check/import_cpp.cpp +++ b/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 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"; + 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); - code_stream << "#include \"" - << FormatEscaped( - context.string_literal_values().Get(import.library_id)) - << "\"\n"; + // 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; } diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 20ecdf3f88ba..d5d79a4d1ab3 100644 --- a/toolchain/check/node_stack.h +++ b/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: diff --git a/toolchain/check/testdata/interop/cpp/bad_import.carbon b/toolchain/check/testdata/interop/cpp/bad_import.carbon index cc1df7c6811a..deae0a010589 100644 --- a/toolchain/check/testdata/interop/cpp/bad_import.carbon +++ b/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++ +} +'''; diff --git a/toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon b/toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon index da199c832707..27281c4835fc 100644 --- a/toolchain/check/testdata/interop/cpp/cpp_diagnostics.carbon +++ b/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 { diff --git a/toolchain/check/testdata/interop/cpp/inline.carbon b/toolchain/check/testdata/interop/cpp/inline.carbon new file mode 100644 index 000000000000..36ff58f34f25 --- /dev/null +++ b/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 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: = 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: +// 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 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: = 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: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon b/toolchain/check/testdata/packages/fail_import_inline_not_cpp.carbon new file mode 100644 index 000000000000..d3ee655163da --- /dev/null +++ b/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"; diff --git a/toolchain/check/testdata/packages/restricted_package_names.carbon b/toolchain/check/testdata/packages/restricted_package_names.carbon index 2e50fb584e3e..af3f80e876fa 100644 --- a/toolchain/check/testdata/packages/restricted_package_names.carbon +++ b/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: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index b094724eb92e..0c0c1bdbdf5d 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index b86ee998c7a6..8e25d0e52b2b 100644 --- a/toolchain/lex/token_kind.def +++ b/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") diff --git a/toolchain/lower/testdata/interop/cpp/import_inline.carbon b/toolchain/lower/testdata/interop/cpp/import_inline.carbon new file mode 100644 index 000000000000..c0786d8285c3 --- /dev/null +++ b/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) diff --git a/toolchain/parse/handle_import_and_package.cpp b/toolchain/parse/handle_import_and_package.cpp index a97c895b3114..92cc47099ec6 100644 --- a/toolchain/parse/handle_import_and_package.cpp +++ b/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( + 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 diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 43697735a983..1a14bcce44e8 100644 --- a/toolchain/parse/node_kind.def +++ b/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) diff --git a/toolchain/parse/testdata/packages/import/cpp_inline.carbon b/toolchain/parse/testdata/packages/import/cpp_inline.carbon new file mode 100644 index 000000000000..e4974e85c60f --- /dev/null +++ b/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: ] diff --git a/toolchain/parse/tree.h b/toolchain/parse/tree.h index 9931bc68ee69..89f965892bab 100644 --- a/toolchain/parse/tree.h +++ b/toolchain/parse/tree.h @@ -80,6 +80,7 @@ class Tree : public Printable { 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; diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index bce980e78548..148bbbc0cd80 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -258,6 +258,18 @@ struct LibrarySpecifier { NodeIdOneOf name; }; +using InlineImportBody = + LeafNode; + +// `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; struct ImportDecl { @@ -283,6 +295,7 @@ struct ImportDecl { llvm::SmallVector modifiers; std::optional name; std::optional library; + std::optional inline_specifier; Lex::SemiTokenIndex token; };