mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 19:30:12 +01:00
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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: ]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user