diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index f59bcd2974cd..0f59ecdc3f7e 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -49,31 +49,37 @@ static auto GenerateLineMarker(Context& context, llvm::raw_ostream& out, << FormatEscaped(context.tokens().source().filename()) << "\"\n"; } +// Appends a line marker and the specified `code` to `out`, adjusting the +// `line` number if the `code_token` represents a block string literal. +static auto AppendInlineCode(Context& context, llvm::raw_ostream& out, + Lex::TokenIndex code_token, llvm::StringRef code) + -> void { + // 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, out, line); + out << code << "\n"; +} + // Generates C++ file contents to #include all requested imports. static auto GenerateCppIncludesHeaderCode( Context& context, llvm::ArrayRef imports) -> std::string { - std::string code; - llvm::raw_string_ostream code_stream(code); + RawStringOstream code_stream; for (const Parse::Tree::PackagingNames& import : imports) { if (import.inline_body_id.has_value()) { // Expand `import Cpp inline "code";` directly into the specified code. auto code_token = context.parse_tree().node_token(import.inline_body_id); - - // Compute the line number on which the C++ code starts. Usually the code - // is specified as a block string literal and starts on the line after the - // start of the string token. - // TODO: Determine if this is a block string literal without calling - // `GetTokenText`, which re-lexes the string. - int line = context.tokens().GetLineNumber(code_token); - if (context.tokens().GetTokenText(code_token).contains('\n')) { - ++line; - } - - GenerateLineMarker(context, code_stream, line); - code_stream << context.string_literal_values().Get( - context.tokens().GetStringLiteralValue(code_token)) - << "\n"; + AppendInlineCode(context, code_stream, code_token, + context.string_literal_values().Get( + context.tokens().GetStringLiteralValue(code_token))); // TODO: Inject a clang pragma here to produce an error if there are // unclosed scopes at the end of this inline C++ fragment. } else if (import.library_id.has_value()) { @@ -90,7 +96,7 @@ static auto GenerateCppIncludesHeaderCode( } } } - return code; + return code_stream.TakeStr(); } // Adds the given source location and an `ImportIRInst` referring to it in @@ -347,6 +353,8 @@ class CarbonExternalASTSource : public clang::ExternalASTSource { bool root_scope_initialized_ = false; }; +} // namespace + void CarbonExternalASTSource::StartTranslationUnit( clang::ASTConsumer* /*Consumer*/) { auto& translation_unit = *ast_context_->getTranslationUnitDecl(); @@ -526,6 +534,33 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( return true; } +// Parses a sequence of top-level declarations and forms a corresponding +// representation in the Clang AST. Unlike clang::ParseAST, does not finish the +// translation unit when EOF is reached. +static auto ParseTopLevelDecls(clang::Parser& parser, + clang::ASTConsumer& consumer) -> void { + // Don't allow C++20 module declarations in inline Cpp code fragments. + auto module_import_state = clang::Sema::ModuleImportState::NotACXX20Module; + + // Parse top-level declarations until we see EOF. Do not parse EOF, as that + // will cause the parser to end the translation unit prematurely. + while (parser.getCurToken().isNot(clang::tok::eof)) { + clang::Parser::DeclGroupPtrTy decl_group; + bool eof = parser.ParseTopLevelDecl(decl_group, module_import_state); + CARBON_CHECK(!eof, "Should not parse decls at EOF"); + if (decl_group && !consumer.HandleTopLevelDecl(decl_group.get())) { + // If the consumer rejects the declaration, bail out of parsing. + // + // TODO: In this case, we shouldn't parse any more declarations even in + // separate inline C++ fragments. But our current AST consumer only ever + // returns true. + break; + } + } +} + +namespace { + // An action and a set of registered Clang callbacks used to generate an AST // from a set of Cpp imports. class GenerateASTAction : public clang::ASTFrontendAction { @@ -583,20 +618,7 @@ class GenerateASTAction : public clang::ASTFrontendAction { context_->set_cpp_context( std::make_unique(clang_instance, std::move(parser_ptr))); - // Don't allow C++20 module declarations in inline Cpp code fragments. - auto module_import_state = clang::Sema::ModuleImportState::NotACXX20Module; - - // Parse top-level declarations until we see EOF. Do not parse EOF, as that - // will cause the parser to end the translation unit prematurely. - while (parser.getCurToken().isNot(clang::tok::eof)) { - clang::Parser::DeclGroupPtrTy decl_group; - bool eof = parser.ParseTopLevelDecl(decl_group, module_import_state); - CARBON_CHECK(!eof); - if (decl_group && !clang_instance.getASTConsumer().HandleTopLevelDecl( - decl_group.get())) { - break; - } - } + ParseTopLevelDecls(parser, clang_instance.getASTConsumer()); } private: @@ -686,6 +708,40 @@ auto GenerateAst(Context& context, return true; } +auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, + llvm::StringRef source_code) -> void { + auto* cpp_context = context.cpp_context(); + CARBON_CHECK(cpp_context); + + clang::Sema& sema = cpp_context->sema(); + clang::Preprocessor& preprocessor = sema.getPreprocessor(); + clang::Parser& parser = cpp_context->parser(); + + RawStringOstream code_stream; + AppendInlineCode(context, code_stream, + context.parse_tree().node_token(loc_id.node_id()), + source_code); + + auto buffer = llvm::MemoryBuffer::getMemBufferCopy(code_stream.TakeStr(), + ""); + clang::FileID file_id = + preprocessor.getSourceManager().createFileID(std::move(buffer)); + + if (preprocessor.EnterSourceFile(file_id, nullptr, clang::SourceLocation())) { + // Clang will have generated a suitable error. There's nothing more to do + // here. + return; + } + + // The parser will typically have an EOF as its cached current token; consume + // that so we can reach the newly-injected tokens. + if (parser.getCurToken().is(clang::tok::eof)) { + parser.ConsumeToken(); + } + + ParseTopLevelDecls(parser, sema.getASTConsumer()); +} + auto FinishAst(Context& context) -> void { if (!context.cpp_context()) { return; diff --git a/toolchain/check/cpp/generate_ast.h b/toolchain/check/cpp/generate_ast.h index b11355341a37..ebf96532d0e7 100644 --- a/toolchain/check/cpp/generate_ast.h +++ b/toolchain/check/cpp/generate_ast.h @@ -5,10 +5,15 @@ #ifndef CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_ #define CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_ +#include + #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/VirtualFileSystem.h" #include "toolchain/check/context.h" +#include "toolchain/parse/tree.h" +#include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -22,6 +27,11 @@ auto GenerateAst(Context& context, std::shared_ptr base_invocation) -> bool; +// Injects C++ code from `inline Cpp` into the active Clang AST context. +// Returns a bool representing whether parsing was successful. +auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, + llvm::StringRef source_code) -> void; + // Finishes AST generation for the given checking context. Performs end of file // steps such as template instantiation and warning on unused declarations. auto FinishAst(Context& context) -> void; diff --git a/toolchain/check/handle_import_and_package.cpp b/toolchain/check/handle_import_and_package.cpp index ac5465c712a3..137ac1911db2 100644 --- a/toolchain/check/handle_import_and_package.cpp +++ b/toolchain/check/handle_import_and_package.cpp @@ -99,8 +99,9 @@ auto HandleParseNode(Context& /*context*/, return true; } -auto HandleParseNode(Context& /*context*/, - Parse::InlineImportBodyId /*node_id*/) -> bool { +auto HandleParseNode(Context& context, Parse::InlineImportBodyId node_id) + -> bool { + context.node_stack().Push(node_id); return true; } diff --git a/toolchain/check/handle_inline_decl.cpp b/toolchain/check/handle_inline_decl.cpp new file mode 100644 index 000000000000..e70530f6f22e --- /dev/null +++ b/toolchain/check/handle_inline_decl.cpp @@ -0,0 +1,56 @@ +// 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 "common/check.h" +#include "toolchain/check/context.h" +#include "toolchain/check/cpp/generate_ast.h" +#include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/check/handle.h" +#include "toolchain/check/inst.h" +#include "toolchain/lex/token_kind.h" +#include "toolchain/parse/node_ids.h" +#include "toolchain/parse/typed_nodes.h" +#include "toolchain/sem_ir/typed_insts.h" + +namespace Carbon::Check { + +auto HandleParseNode(Context& /*context*/, + Parse::InlineIntroducerId /*node_id*/) -> bool { + return true; +} + +auto HandleParseNode(Context& context, Parse::InlineCppDeclId node_id) -> bool { + auto body_id = context.node_stack() + .PopForSoloNodeId(); + + // If there are no Cpp imports prior to this point, the `Cpp` expression after + // `import` will have already produced an error. + // + // TODO: It'd be nice to produce a clearer error saying to insert an `import + // Cpp` in a file that uses `inline Cpp` and doesn't otherwise import anything + // from package `Cpp`. + if (context.constant_values().Get( + context.node_stack().Pop()) == + SemIR::ErrorInst::ConstantId) { + return true; + } + CARBON_CHECK(context.cpp_context(), "Have `Cpp` name but no Cpp context"); + + auto string_token = context.parse_tree().node_token(body_id); + auto string_value_id = context.tokens().GetStringLiteralValue(string_token); + auto string_value = context.string_literal_values().Get(string_value_id); + + if (context.scope_stack().PeekIndex() != ScopeIndex::Package) { + CARBON_DIAGNOSTIC(InlineDeclNotAtFileScope, Error, + "`inline Cpp` declaration not at file scope"); + context.emitter().Emit(node_id, InlineDeclNotAtFileScope); + return true; + } + + InjectAstFromInlineCode(context, SemIR::LocId(body_id), string_value); + AddInst(context, node_id, {.text_id = string_value_id}); + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index cc09e63b339e..e59811e3a91d 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -478,6 +478,8 @@ class NodeStack { case Parse::NodeKind::TuplePatternStart: case Parse::NodeKind::VariableInitializer: case Parse::NodeKind::VariableIntroducer: + case Parse::NodeKind::InlineImportBody: + case Parse::NodeKind::InlineIntroducer: return Id::Kind::None; case Parse::NodeKind::AdaptIntroducer: case Parse::NodeKind::AliasInitializer: @@ -511,7 +513,6 @@ class NodeStack { case Parse::NodeKind::LibraryIntroducer: case Parse::NodeKind::LibrarySpecifier: case Parse::NodeKind::InlineImportSpecifier: - case Parse::NodeKind::InlineImportBody: case Parse::NodeKind::MatchCase: case Parse::NodeKind::MatchCaseGuard: case Parse::NodeKind::MatchCaseGuardIntroducer: diff --git a/toolchain/check/testdata/interop/cpp/inline_decl.carbon b/toolchain/check/testdata/interop/cpp/inline_decl.carbon new file mode 100644 index 000000000000..f417303c551d --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/inline_decl.carbon @@ -0,0 +1,141 @@ +// 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_decl.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/inline_decl.carbon + +// --- inline_decl.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +inline Cpp ''' +void func1() {} +'''; + +fn Run1() { + Cpp.func1(); +} + +inline Cpp ''' +void func2() { + Carbon::Run1(); +} +'''; + +fn Run2() { + Cpp.func2(); +} + +// --- fail_redefinition.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +inline Cpp ''' +void f() {} +'''; + +inline Cpp ''' +// CHECK:STDERR: fail_redefinition.carbon:[[@LINE+7]]:6: error: redefinition of 'f' [CppInteropParseError] +// CHECK:STDERR: 18 | void f() {} +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_redefinition.carbon:[[@LINE-7]]:6: note: previous definition is here [CppInteropParseNote] +// CHECK:STDERR: 7 | void f() {} +// CHECK:STDERR: | ^ +// CHECK:STDERR: +void f() {} +'''; + +// --- fail_diag_location.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +// CHECK:STDERR: fail_diag_location.carbon:[[@LINE+4]]:12: error: use of undeclared identifier 'undeclared' [CppInteropParseError] +// CHECK:STDERR: 10 | void f() { undeclared = 0; } +// CHECK:STDERR: | ^~~~~~~~~~ +// CHECK:STDERR: +inline Cpp "void f() { undeclared = 0; }"; + +inline Cpp ''' +void g() { + // CHECK:STDERR: fail_diag_location.carbon:[[@LINE+4]]:3: error: use of undeclared identifier 'undeclared' [CppInteropParseError] + // CHECK:STDERR: 18 | undeclared = 0; + // CHECK:STDERR: | ^~~~~~~~~~ + // CHECK:STDERR: + undeclared = 0; +} +'''; + +// --- fail_inline_no_import.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_inline_no_import.carbon:[[@LINE+4]]:8: error: name `Cpp` not found [NameNotFound] +// CHECK:STDERR: inline Cpp ""; +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +inline Cpp ""; + +// --- fail_nested.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +class C { + // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:3: error: `inline Cpp` declaration not at file scope [InlineDeclNotAtFileScope] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + inline Cpp ""; +} + +interface I { + // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:3: error: `inline Cpp` declaration not at file scope [InlineDeclNotAtFileScope] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + inline Cpp ""; +} + +constraint N { + // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:3: error: `inline Cpp` declaration not at file scope [InlineDeclNotAtFileScope] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + inline Cpp ""; +} + +impl C as I { + // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:3: error: `inline Cpp` declaration not at file scope [InlineDeclNotAtFileScope] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~~~~~~~~~ + // CHECK:STDERR: + inline Cpp ""; +} + +// --- fail_nested_fn.carbon + +fn F() { + // TODO: Should this be a check error rather than a parse error? + // CHECK:STDERR: fail_nested_fn.carbon:[[@LINE+8]]:3: error: expected expression [ExpectedExpr] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_nested_fn.carbon:[[@LINE+4]]:3: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] + // CHECK:STDERR: inline Cpp ""; + // CHECK:STDERR: ^~~~~~ + // CHECK:STDERR: + inline Cpp ""; +} diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 93fbcd5394bb..b180000113c5 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -131,6 +131,11 @@ CARBON_DIAGNOSTIC_KIND(UnexpectedTokenAfterListElement) CARBON_DIAGNOSTIC_KIND(UnrecognizedDecl) CARBON_DIAGNOSTIC_KIND(UnexpectedTokenInCompoundMemberAccess) +// Inline declaration diagnostics. +CARBON_DIAGNOSTIC_KIND(ExpectedCppAfterInline) +CARBON_DIAGNOSTIC_KIND(ExpectedStringAfterInlineCpp) +CARBON_DIAGNOSTIC_KIND(InlineDeclNotAtFileScope) + // Match diagnostics. CARBON_DIAGNOSTIC_KIND(ExpectedMatchCaseArrow) CARBON_DIAGNOSTIC_KIND(ExpectedMatchCaseBlock) diff --git a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon index 933b5d5f8b77..9c049d77f8fc 100644 --- a/toolchain/lower/testdata/interop/cpp/reverse/function.carbon +++ b/toolchain/lower/testdata/interop/cpp/reverse/function.carbon @@ -24,6 +24,28 @@ void G() { } '''; +// --- single_file.carbon + +library "[[@TEST_NAME]]"; + +import Cpp; + +fn F() {} + +inline Cpp ''' +inline void G1() { + Carbon::F(); +} + +// Call an inline function indirectly to ensure declarations are getting +// properly registered with the CodeGen consumer. +inline void G2() { + G1(); +} +'''; + +fn H() { Cpp.G2(); } + // CHECK:STDOUT: ; ModuleID = 'other.carbon' // CHECK:STDOUT: source_filename = "other.carbon" // CHECK:STDOUT: @@ -78,3 +100,65 @@ void G() { // CHECK:STDOUT: !8 = !{!"int", !9, i64 0} // CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0} // CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"} +// CHECK:STDOUT: ; ModuleID = 'single_file.carbon' +// CHECK:STDOUT: source_filename = "single_file.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: $_Z2G2v = comdat any +// CHECK:STDOUT: +// CHECK:STDOUT: $_Z2G1v = comdat any +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CF.Main() #0 !dbg !11 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CH.Main() #0 !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_Z2G2v(), !dbg !16 +// CHECK:STDOUT: ret void, !dbg !17 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress uwtable +// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G2v() #1 comdat { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_Z2G1v() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: inlinehint mustprogress nounwind uwtable +// CHECK:STDOUT: define linkonce_odr dso_local void @_Z2G1v() #2 comdat { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_CF.Main() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nounwind } +// CHECK:STDOUT: attributes #1 = { inlinehint mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #2 = { inlinehint mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "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: !llvm.errno.tbaa = !{!7} +// 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 8, !"PIC Level", i32 2} +// CHECK:STDOUT: !3 = !{i32 7, !"PIE Level", i32 2} +// CHECK:STDOUT: !4 = !{i32 7, !"uwtable", i32 2} +// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !6 = !DIFile(filename: "single_file.carbon", directory: "") +// CHECK:STDOUT: !7 = !{!8, !8, i64 0} +// CHECK:STDOUT: !8 = !{!"int", !9, i64 0} +// CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0} +// CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"} +// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !6, line: 6, type: !12, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !12 = !DISubroutineType(types: !13) +// CHECK:STDOUT: !13 = !{null} +// CHECK:STDOUT: !14 = !DILocation(line: 6, column: 1, scope: !11) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "H", linkageName: "_CH.Main", scope: null, file: !6, line: 20, type: !12, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !16 = !DILocation(line: 20, column: 10, scope: !15) +// CHECK:STDOUT: !17 = !DILocation(line: 20, column: 1, scope: !15) diff --git a/toolchain/parse/handle_decl_scope_loop.cpp b/toolchain/parse/handle_decl_scope_loop.cpp index 168ddcfb6efb..049867968e05 100644 --- a/toolchain/parse/handle_decl_scope_loop.cpp +++ b/toolchain/parse/handle_decl_scope_loop.cpp @@ -147,6 +147,9 @@ static constexpr auto DeclIntroducers = [] { set_contextual(Lex::TokenKind::Var, ClassContext, NodeKind::FieldIntroducer, StateKind::FieldDecl); + set(Lex::TokenKind::Inline, NodeKind::InlineIntroducer, + StateKind::InlineDeclAfterIntroducer); + set_packaging(Lex::TokenKind::Package, NodeKind::PackageIntroducer, StateKind::Package); set_packaging(Lex::TokenKind::Library, NodeKind::LibraryIntroducer, @@ -226,6 +229,7 @@ static auto ResolveAmbiguousTokenAsDeclaration(Context& context, case Lex::TokenKind::Extern: case Lex::TokenKind::Fn: case Lex::TokenKind::Import: + case Lex::TokenKind::Inline: case Lex::TokenKind::Interface: case Lex::TokenKind::Let: case Lex::TokenKind::Library: diff --git a/toolchain/parse/handle_inline_decl.cpp b/toolchain/parse/handle_inline_decl.cpp new file mode 100644 index 000000000000..2a6ca422ed59 --- /dev/null +++ b/toolchain/parse/handle_inline_decl.cpp @@ -0,0 +1,43 @@ +// 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 "toolchain/lex/token_kind.h" +#include "toolchain/parse/context.h" +#include "toolchain/parse/handle.h" +#include "toolchain/parse/node_kind.h" +#include "toolchain/parse/state.h" + +namespace Carbon::Parse { + +auto HandleInlineDeclAfterIntroducer(Context& context) -> void { + auto state = context.PopState(); + + if (auto cpp_token = context.ConsumeIf(Lex::TokenKind::Cpp)) { + context.AddLeafNode(NodeKind::CppNameExpr, *cpp_token); + } else { + CARBON_DIAGNOSTIC(ExpectedCppAfterInline, Error, + "expected `Cpp` after `inline`"); + context.emitter().Emit(*context.position(), ExpectedCppAfterInline); + context.AddNode(NodeKind::InlineCppDecl, + context.SkipPastLikelyEnd(state.token), + /*has_error=*/true); + return; + } + + if (auto str = context.ConsumeIf(Lex::TokenKind::StringLiteral)) { + context.AddLeafNode(NodeKind::InlineImportBody, *str); + context.AddNodeExpectingDeclSemi(state, NodeKind::InlineCppDecl, + Lex::TokenKind::Inline, + /*is_def_allowed=*/false); + } else { + CARBON_DIAGNOSTIC(ExpectedStringAfterInlineCpp, Error, + "expected string literal after `inline Cpp`"); + context.emitter().Emit(*context.position(), ExpectedStringAfterInlineCpp); + context.AddNode(NodeKind::InlineCppDecl, + context.SkipPastLikelyEnd(state.token), + /*has_error=*/true); + } +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 173cc4ced0ce..67a2e1724dd7 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -130,6 +130,9 @@ CARBON_PARSE_NODE_KIND(LibrarySpecifier) CARBON_PARSE_NODE_KIND(InlineImportSpecifier) CARBON_PARSE_NODE_KIND(InlineImportBody) +CARBON_PARSE_NODE_KIND(InlineIntroducer) +CARBON_PARSE_NODE_KIND(InlineCppDecl) + CARBON_PARSE_NODE_KIND(IdentifierNameQualifierWithParams) CARBON_PARSE_NODE_KIND(IdentifierNameQualifierWithoutParams) diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 999a0eb4da01..2d3a8ad66194 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -368,6 +368,10 @@ CARBON_PARSE_STATE(DeclNameAndParamsAfterParams) // ^~~~~~ // 1. Import // +// inline ... +// ^~~~~~ +// 1. InlineDeclAfterIntroducer (variant is Regular) +// // interface ... // ^~~~~~~~~ // 1. TypeAfterIntroducerAsInterface @@ -433,6 +437,13 @@ CARBON_PARSE_STATE_VARIANTS3(Decl, Class, Interface, Regular) // CARBON_PARSE_STATE_VARIANTS3(DeclScopeLoop, Class, Interface, Regular) +// Handles processing of an inline declaration. +// +// inline Cpp "..." ; +// ^~~~~~~~~~~ +// (state done) +CARBON_PARSE_STATE(InlineDeclAfterIntroducer) + // Handles periods. Only does one `.` segment; the source is // responsible for handling chaining. // diff --git a/toolchain/parse/testdata/packages/import/cpp_inline.carbon b/toolchain/parse/testdata/packages/import/cpp_inline.carbon index 93e7b25f9ad3..2251b54b9036 100644 --- a/toolchain/parse/testdata/packages/import/cpp_inline.carbon +++ b/toolchain/parse/testdata/packages/import/cpp_inline.carbon @@ -72,6 +72,52 @@ import Cpp inline ''' int n; '''; +// --- inline_cpp.carbon + +inline Cpp ''' +int n; +'''; + +// --- fail_inline_cpp_syntax_errors.carbon + +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+4]]:7: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] +// CHECK:STDERR: inline; +// CHECK:STDERR: ^ +// CHECK:STDERR: +inline; + +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+4]]:8: error: expected `Cpp` after `inline` [ExpectedCppAfterInline] +// CHECK:STDERR: inline ''' +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +inline ''' +int n; +'''; + +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+4]]:11: error: expected string literal after `inline Cpp` [ExpectedStringAfterInlineCpp] +// CHECK:STDERR: inline Cpp; +// CHECK:STDERR: ^ +// CHECK:STDERR: +inline Cpp; + +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+4]]:12: error: expected string literal after `inline Cpp` [ExpectedStringAfterInlineCpp] +// CHECK:STDERR: inline Cpp x; +// CHECK:STDERR: ^ +// CHECK:STDERR: +inline Cpp x; + +inline Cpp ''' +int n; +''' +// CHECK:STDERR: fail_inline_cpp_syntax_errors.carbon:[[@LINE+4]]:1: error: `inline` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: +// CHECK:STDERR: ^ +// CHECK:STDERR: + +// --- eof.carbon +// This split exists to avoid the "no `;` at EOF" diagnostic above pointing to +// the last CHECK:STDOUT line below. + // CHECK:STDOUT: - filename: fail_bad_syntax.carbon // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, @@ -150,3 +196,42 @@ int n; // CHECK:STDOUT: {kind: 'ImportDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: inline_cpp.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'CppNameExpr', text: 'Cpp'}, +// CHECK:STDOUT: {kind: 'InlineImportBody', text: '''' +// CHECK:STDOUT: int n; +// CHECK:STDOUT: ''''}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: ';', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_inline_cpp_syntax_errors.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: ';', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'CppNameExpr', text: 'Cpp'}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'CppNameExpr', text: 'Cpp'}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InlineIntroducer', text: 'inline'}, +// CHECK:STDOUT: {kind: 'CppNameExpr', text: 'Cpp'}, +// CHECK:STDOUT: {kind: 'InlineImportBody', text: '''' +// CHECK:STDOUT: int n; +// CHECK:STDOUT: ''''}, +// CHECK:STDOUT: {kind: 'InlineCppDecl', text: '''' +// CHECK:STDOUT: int n; +// CHECK:STDOUT: '''', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: eof.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index e956deb6cf77..8bb5aabac1aa 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -251,6 +251,18 @@ struct InlineImportSpecifier { InlineImportBodyId body; }; +using InlineIntroducer = + LeafNode; +struct InlineCppDecl { + static constexpr auto Kind = NodeKind::InlineCppDecl.Define( + {.category = NodeCategory::Decl, .bracketed_by = InlineIntroducer::Kind}); + + InlineIntroducerId introducer; + CppNameExprId cpp_name; + InlineImportBodyId body; + Lex::SemiTokenIndex token; +}; + // First line of the file, such as: // `impl package MyPackage library "MyLibrary";` struct PackageDecl { diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 07e2c13fdac6..14189ef9ced5 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -90,8 +90,9 @@ CARBON_SEM_IR_INST_KIND(ImportCppDecl) CARBON_SEM_IR_INST_KIND(ImportDecl) CARBON_SEM_IR_INST_KIND(ImportRefLoaded) CARBON_SEM_IR_INST_KIND(ImportRefUnloaded) -CARBON_SEM_IR_INST_KIND(InitForm) CARBON_SEM_IR_INST_KIND(InPlaceInit) +CARBON_SEM_IR_INST_KIND(InitForm) +CARBON_SEM_IR_INST_KIND(InlineCppDecl) CARBON_SEM_IR_INST_KIND(InstType) CARBON_SEM_IR_INST_KIND(InstValue) CARBON_SEM_IR_INST_KIND(IntLiteralType) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index d704e51f3618..900af42f54c9 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -1049,6 +1049,16 @@ struct ImportCppDecl { .is_lowered = false}); }; +// An `inline Cpp` declaration. +struct InlineCppDecl { + static constexpr auto Kind = + InstKind::InlineCppDecl.Define( + {.ir_name = "inline_cpp", + .constant_kind = InstConstantKind::Never, + .is_lowered = false}); + StringLiteralValueId text_id; +}; + // An `import` declaration. This is mainly for `import` diagnostics, and a 1:1 // correspondence with actual `import`s isn't guaranteed. struct ImportDecl {