Add support for inline Cpp declarations. (#6994)

For #6830, add support for inline C++ fragments as a declaration rather
than as a packaging directive. For now, this uses `inline Cpp
<string-literal>;` as syntax. The prior `import Cpp inline
<string-literal>;` is left alone for the time being. We can decide
separately whether to remove that.

`inline Cpp` requires that there was at least one `import Cpp`. It's not
clear to me if that's the right design long-term, but it seems
reasonable for now.

Assisted-by: Gemini via Google Antigravity
This commit is contained in:
Richard Smith
2026-03-31 22:27:43 +00:00
committed by GitHub
parent 47e9d62fd5
commit 8b59e85b16
16 changed files with 559 additions and 36 deletions
+88 -32
View File
@@ -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<Parse::Tree::PackagingNames> 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<CppContext>(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(),
"<inline c++>");
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;
+10
View File
@@ -5,10 +5,15 @@
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_
#define CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_
#include <memory>
#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<clang::CompilerInvocation> 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;
@@ -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;
}
+56
View File
@@ -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<Parse::NodeKind::InlineImportBody>();
// 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<Parse::NodeKind::CppNameExpr>()) ==
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<SemIR::InlineCppDecl>(context, node_id, {.text_id = string_value_id});
return true;
}
} // namespace Carbon::Check
+2 -1
View File
@@ -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:
+141
View File
@@ -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 "";
}
+5
View File
@@ -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)
@@ -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)
@@ -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:
+43
View File
@@ -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
+3
View File
@@ -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)
+11
View File
@@ -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 `.<expression>` segment; the source is
// responsible for handling chaining.
//
@@ -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: ]
+12
View File
@@ -251,6 +251,18 @@ struct InlineImportSpecifier {
InlineImportBodyId body;
};
using InlineIntroducer =
LeafNode<NodeKind::InlineIntroducer, Lex::InlineTokenIndex>;
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 {
+2 -1
View File
@@ -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)
+10
View File
@@ -1049,6 +1049,16 @@ struct ImportCppDecl {
.is_lowered = false});
};
// An `inline Cpp` declaration.
struct InlineCppDecl {
static constexpr auto Kind =
InstKind::InlineCppDecl.Define<Parse::InlineCppDeclId>(
{.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 {