Explorar o código

Add support for importing a trivial global C++ function (#5033)

ASTUnit is owned by `CompileSubcommand`, passed through `Unit` to be
populated in `ImportCppFiles()` and used via `SemIR::File`.
When generating the AST, pass `-x c++` args to compile C++ (temporary
until we pass args properly).
`Cpp` namespace is marked as a special namespace and has dedicated logic
in `LookupNameInExactScope()`.
The logic for importing declarations from C++ to Carbon is in
`import_cpp.cpp`, but we're likely to want to refactor this
signfiicantly over time as it grows (perhaps a dedicated directory?).

Part of #4666.
Boaz Brickner hai 1 ano
pai
achega
87b9cab7b1

+ 1 - 0
toolchain/check/BUILD

@@ -112,6 +112,7 @@ cc_library(
         "//toolchain/sem_ir:inst",
         "//toolchain/sem_ir:typed_insts",
         "@llvm-project//clang:frontend",
+        "@llvm-project//clang:sema",
         "@llvm-project//clang:tooling",
         "@llvm-project//llvm:Support",
     ],

+ 3 - 0
toolchain/check/check.h

@@ -27,6 +27,9 @@ struct Unit {
 
   // The unit's SemIR, provided as empty and filled in by CheckParseTrees.
   SemIR::File* sem_ir;
+
+  // The Clang AST owned by `CompileSubcommand`.
+  std::unique_ptr<clang::ASTUnit>* cpp_ast = nullptr;
 };
 
 // Checks a group of parse trees. This will use imports to decide the order of

+ 10 - 2
toolchain/check/check_unit.cpp

@@ -135,8 +135,16 @@ auto CheckUnit::InitPackageScopeAndImports() -> void {
   ImportCurrentPackage(package_inst_id, namespace_type_id);
   CARBON_CHECK(context_.scope_stack().PeekIndex() == ScopeIndex::Package);
   ImportOtherPackages(namespace_type_id);
-  ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
-                 unit_and_imports_->cpp_import_names, fs_);
+
+  const auto& cpp_import_names = unit_and_imports_->cpp_import_names;
+  if (!cpp_import_names.empty()) {
+    auto* cpp_ast = unit_and_imports_->unit->cpp_ast;
+    CARBON_CHECK(cpp_ast);
+    CARBON_CHECK(!cpp_ast->get());
+    *cpp_ast =
+        ImportCppFiles(context_, unit_and_imports_->unit->sem_ir->filename(),
+                       cpp_import_names, fs_);
+  }
 }
 
 auto CheckUnit::CollectDirectImports(

+ 164 - 6
toolchain/check/import_cpp.cpp

@@ -9,6 +9,7 @@
 #include <string>
 
 #include "clang/Frontend/TextDiagnosticPrinter.h"
+#include "clang/Sema/Lookup.h"
 #include "clang/Tooling/Tooling.h"
 #include "common/raw_string_ostream.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
@@ -21,6 +22,7 @@
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/diagnostic.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/name_scope.h"
 
 namespace Carbon::Check {
@@ -61,9 +63,10 @@ static auto GenerateAst(Context& context, llvm::StringRef importing_file_path,
                                                     diagnostic_options.get());
   // TODO: Share compilation flags with ClangRunner.
   auto ast = clang::tooling::buildASTFromCodeWithArgs(
-      GenerateCppIncludesHeaderCode(context, imports), {},
-      (importing_file_path + ".generated.cpp_imports.h").str(), "clang-tool",
-      std::make_shared<clang::PCHContainerOperations>(),
+      GenerateCppIncludesHeaderCode(context, imports),
+      // Parse C++ (and not C)
+      {"-x", "c++"}, (importing_file_path + ".generated.cpp_imports.h").str(),
+      "clang-tool", std::make_shared<clang::PCHContainerOperations>(),
       clang::tooling::getClangStripDependencyFileAdjuster(),
       clang::tooling::FileContentMappings(), &diagnostics_consumer, fs);
   // TODO: Implement and use a DynamicRecursiveASTVisitor to traverse the AST.
@@ -115,12 +118,14 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
 auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
                     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
-    -> void {
+    -> std::unique_ptr<clang::ASTUnit> {
   if (imports.empty()) {
-    return;
+    return nullptr;
   }
 
-  auto [ast, ast_has_error] =
+  CARBON_CHECK(!context.sem_ir().cpp_ast());
+
+  auto [generated_ast, ast_has_error] =
       GenerateAst(context, importing_file_path, imports, fs);
 
   PackageNameId package_id = imports.front().package_id;
@@ -131,10 +136,163 @@ auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
   auto name_scope_id = AddNamespace(context, package_id, imports);
   SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
   name_scope.set_is_closed_import(true);
+  name_scope.set_is_cpp_scope(true);
+
+  context.sem_ir().set_cpp_ast(generated_ast.get());
 
   if (ast_has_error) {
     name_scope.set_has_error();
   }
+
+  return std::move(generated_ast);
+}
+
+// Look ups the given name in the Clang AST. Returns the lookup result if lookup
+// was successful.
+static auto ClangLookup(Context& context, SemIR::LocId loc_id,
+                        SemIR::NameId name_id)
+    -> std::optional<clang::LookupResult> {
+  std::optional<llvm::StringRef> name =
+      context.names().GetAsStringIfIdentifier(name_id);
+  if (!name) {
+    // Special names never exist in C++ code.
+    return std::nullopt;
+  }
+
+  clang::ASTUnit* ast = context.sem_ir().cpp_ast();
+  CARBON_CHECK(ast);
+  clang::Sema& sema = ast->getSema();
+
+  clang::LookupResult lookup(
+      sema,
+      clang::DeclarationNameInfo(
+          clang::DeclarationName(
+              sema.getPreprocessor().getIdentifierInfo(*name)),
+          clang::SourceLocation()),
+      clang::Sema::LookupNameKind::LookupOrdinaryName);
+
+  bool found = sema.LookupQualifiedName(
+      lookup, ast->getASTContext().getTranslationUnitDecl());
+
+  if (lookup.isClassLookup()) {
+    // TODO: To support class lookup, also return the AccessKind for storage.
+    context.TODO(loc_id, "Unsupported: Lookup in Class");
+    return std::nullopt;
+  }
+
+  if (!found) {
+    return std::nullopt;
+  }
+
+  return lookup;
+}
+
+// Imports a function declaration from Clang to Carbon. If successful, returns
+// the new Carbon function declaration `InstId`.
+static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
+                               SemIR::NameScopeId scope_id,
+                               SemIR::NameId name_id,
+                               const clang::FunctionDecl* clang_decl)
+    -> SemIR::InstId {
+  if (clang_decl->isVariadic()) {
+    context.TODO(loc_id, "Unsupported: Variadic function");
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+  if (!clang_decl->isGlobal()) {
+    context.TODO(loc_id, "Unsupported: Non-global function");
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+  if (clang_decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate) {
+    context.TODO(loc_id, "Unsupported: Template function");
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+  if (!clang_decl->param_empty()) {
+    context.TODO(loc_id, "Unsupported: Function with parameters");
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+  if (!clang_decl->getReturnType()->isVoidType()) {
+    context.TODO(loc_id, "Unsupported: Function with non-void return type");
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+
+  auto function_decl = SemIR::FunctionDecl{
+      SemIR::TypeId::None, SemIR::FunctionId::None, SemIR::InstBlockId::Empty};
+  auto decl_id = AddPlaceholderInst(
+      context, SemIR::LocIdAndInst(Parse::NodeId::None, function_decl));
+
+  auto function_info = SemIR::Function{
+      {.name_id = name_id,
+       .parent_scope_id = scope_id,
+       .generic_id = SemIR::GenericId::None,
+       .first_param_node_id = Parse::NodeId::None,
+       .last_param_node_id = Parse::NodeId::None,
+       .pattern_block_id = SemIR::InstBlockId::Empty,
+       .implicit_param_patterns_id = SemIR::InstBlockId::Empty,
+       .param_patterns_id = SemIR::InstBlockId::Empty,
+       .call_params_id = SemIR::InstBlockId::Empty,
+       .is_extern = false,
+       .extern_library_id = SemIR::LibraryNameId::None,
+       .non_owning_decl_id = SemIR::InstId::None,
+       .first_owning_decl_id = decl_id,
+       .definition_id = SemIR::InstId::None},
+      {.return_slot_pattern_id = SemIR::InstId::None,
+       .virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
+       .self_param_id = SemIR::InstId::None}};
+
+  function_decl.function_id = context.functions().Add(function_info);
+
+  function_decl.type_id = GetFunctionType(context, function_decl.function_id,
+                                          SemIR::SpecificId::None);
+
+  ReplaceInstBeforeConstantUse(context, decl_id, function_decl);
+
+  return decl_id;
+}
+
+// Imports a declaration from Clang to Carbon. If successful, returns the
+// instruction for the new Carbon declaration.
+static auto ImportNameDecl(Context& context, SemIR::LocId loc_id,
+                           SemIR::NameScopeId scope_id, SemIR::NameId name_id,
+                           const clang::NamedDecl* clang_decl)
+    -> SemIR::InstId {
+  if (const auto* clang_function_decl =
+          clang::dyn_cast<clang::FunctionDecl>(clang_decl)) {
+    return ImportFunctionDecl(context, loc_id, scope_id, name_id,
+                              clang_function_decl);
+  }
+
+  context.TODO(loc_id, llvm::formatv("Unsupported: Declaration type {0}",
+                                     clang_decl->getDeclKindName())
+                           .str());
+  return SemIR::InstId::None;
+}
+
+auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
+                       SemIR::NameScopeId scope_id, SemIR::NameId name_id)
+    -> SemIR::InstId {
+  auto lookup = ClangLookup(context, loc_id, name_id);
+  if (!lookup) {
+    return SemIR::InstId::None;
+  }
+
+  DiagnosticAnnotationScope annotate_diagnostics(
+      &context.emitter(), [&](auto& builder) {
+        CARBON_DIAGNOSTIC(InCppNameLookup, Note,
+                          "in `Cpp` name lookup for `{0}`", SemIR::NameId);
+        builder.Note(loc_id, InCppNameLookup, name_id);
+      });
+
+  if (!lookup->isSingleResult()) {
+    context.TODO(loc_id,
+                 llvm::formatv("Unsupported: Lookup succeeded but couldn't "
+                               "find a single result; LookupResultKind: {0}",
+                               lookup->getResultKind())
+                     .str());
+    return SemIR::ErrorInst::SingletonInstId;
+  }
+
+  return ImportNameDecl(context, loc_id, scope_id, name_id,
+                        lookup->getFoundDecl());
 }
 
 }  // namespace Carbon::Check

+ 11 - 3
toolchain/check/import_cpp.h

@@ -11,11 +11,19 @@
 
 namespace Carbon::Check {
 
-// Generates a C++ header that includes the imported cpp files, parses it and
-// report errors and warnings. If successful, adds a `Cpp` namespace.
+// Generates a C++ header that includes the imported cpp files, parses it,
+// generates the AST from it and links `SemIR::File` to it. Report C++ errors
+// and warnings. If successful, adds a `Cpp` namespace and returns the AST.
 auto ImportCppFiles(Context& context, llvm::StringRef importing_file_path,
                     llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
-                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) -> void;
+                    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs)
+    -> std::unique_ptr<clang::ASTUnit>;
+
+// Looks up the given name in the Clang AST generated when importing C++ code.
+// If successful, generates the instruction and returns the new `InstId`.
+auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
+                       SemIR::NameScopeId scope_id, SemIR::NameId name_id)
+    -> SemIR::InstId;
 
 }  // namespace Carbon::Check
 

+ 14 - 0
toolchain/check/name_lookup.cpp

@@ -6,9 +6,11 @@
 
 #include "toolchain/check/generic.h"
 #include "toolchain/check/import.h"
+#include "toolchain/check/import_cpp.h"
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/type_completion.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/sem_ir/name_scope.h"
 
 namespace Carbon::Check {
 
@@ -150,6 +152,18 @@ auto LookupNameInExactScope(Context& context, SemIR::LocId loc_id,
                                    scope.import_ir_scopes(), name_id),
         SemIR::AccessKind::Public);
   }
+
+  if (scope.is_cpp_scope()) {
+    SemIR::InstId imported_inst_id =
+        ImportNameFromCpp(context, loc_id, scope_id, name_id);
+    if (imported_inst_id.has_value()) {
+      SemIR::ScopeLookupResult result = SemIR::ScopeLookupResult::MakeFound(
+          imported_inst_id, SemIR::AccessKind::Public);
+      scope.AddRequired({.name_id = name_id, .result = result});
+      return result;
+    }
+  }
+
   return SemIR::ScopeLookupResult::MakeNotFound();
 }
 

+ 488 - 1
toolchain/check/testdata/interop/cpp/no_prelude/function_decl.carbon

@@ -18,18 +18,505 @@ library "[[@TEST_NAME]]";
 
 import Cpp library "function_decl.h";
 
+fn MyF() {
+  Cpp.foo();
+}
+
+// --- fail_import_function_decl_use_different_name.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "function_decl.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_function_decl_use_different_name.carbon:[[@LINE+4]]:3: error: member name `bar` not found in `Cpp` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.bar();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.bar();
+}
+
+// --- function_special_name_decl.h
+
+void base();
+
+// --- fail_import_function_special_name_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "function_special_name_decl.h";
+
+fn MyF() {
+  // CHECK:STDERR: fail_import_function_special_name_decl.carbon:[[@LINE+4]]:3: error: member name `base` not found in `Cpp` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.base();
+  // CHECK:STDERR:   ^~~~~~~~
+  // CHECK:STDERR:
+  Cpp.base();
+}
+
+// --- import_function_escaped_special_name_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "function_special_name_decl.h";
+
+fn MyF() {
+  Cpp.r#base();
+}
+
+// --- overloaded_function_decl.h
+
+void foo();
+void foo(int value);
+
+// --- fail_import_overloaded_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "overloaded_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_overloaded_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_overloaded_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// --- variadic_function_decl.h
+
+void foo(int...);
+
+// --- fail_import_variadic_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "variadic_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_variadic_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Variadic function` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_variadic_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// --- non_global_function_decl.h
+
+static void foo();
+
+// --- fail_import_non_global_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "non_global_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_non_global_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Non-global function` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_non_global_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// TODO: Test that template functions are unsupported.
+//       This is not tested because template functions are not considered a single result when doing lookup.
+
+// --- parameterized_function_decl.h
+
+void foo(int);
+
+// --- fail_import_parameterized_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "parameterized_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_parameterized_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Function with parameters` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_parameterized_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// --- non_void_return_function_decl.h
+
+int foo();
+
+// --- fail_import_non_void_return_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "non_void_return_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_non_void_return_function_decl.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Function with non-void return type` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_non_void_return_function_decl.carbon:[[@LINE+4]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
+// --- non_function_decl.h
+
+class foo;
+
+// --- fail_import_non_function_decl.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp library "non_function_decl.h";
+
+fn F() {
+  // CHECK:STDERR: fail_import_non_function_decl.carbon:[[@LINE+11]]:3: error: semantics TODO: `Unsupported: Declaration type CXXRecord` [SemanticsTodo]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR: fail_import_non_function_decl.carbon:[[@LINE+8]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_import_non_function_decl.carbon:[[@LINE+4]]:3: error: member name `foo` not found in `Cpp` [MemberNameNotFoundInScope]
+  // CHECK:STDERR:   Cpp.foo();
+  // CHECK:STDERR:   ^~~~~~~
+  // CHECK:STDERR:
+  Cpp.foo();
+}
+
 // CHECK:STDOUT: --- import_function_decl.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %foo.type: type = fn_type @foo [concrete]
+// CHECK:STDOUT:   %foo: %foo.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: imports {
-// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = @MyF.%foo.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.decl: %foo.type = fn_decl @foo [concrete = constants.%foo] {} {}
+// CHECK:STDOUT:   %foo.ref: %foo.type = name_ref foo, %foo.decl [concrete = constants.%foo]
+// CHECK:STDOUT:   %foo.call: init %empty_tuple.type = call %foo.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @foo[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_function_decl_use_different_name.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .bar = <poisoned>
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
 // CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
 // CHECK:STDOUT:     import Cpp "function_decl.h"
 // CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %bar.ref: <error> = name_ref bar, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_function_special_name_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "function_special_name_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %base.ref: <error> = name_ref base, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- import_function_escaped_special_name_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %MyF.type: type = fn_type @MyF [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %MyF: %MyF.type = struct_value () [concrete]
+// CHECK:STDOUT:   %base.type: type = fn_type @base [concrete]
+// CHECK:STDOUT:   %base: %base.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .r#base = @MyF.%base.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .MyF = %MyF.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "function_special_name_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @MyF() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %base.decl: %base.type = fn_decl @base [concrete = constants.%base] {} {}
+// CHECK:STDOUT:   %base.ref: %base.type = name_ref r#base, %base.decl [concrete = constants.%base]
+// CHECK:STDOUT:   %base.call: init %empty_tuple.type = call %base.ref()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @base[]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_overloaded_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "overloaded_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_variadic_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "variadic_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_non_global_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "non_global_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_parameterized_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "parameterized_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_non_void_return_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <error>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "non_void_return_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_import_non_function_decl.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Cpp: <namespace> = namespace file.%Cpp.import_cpp, [concrete] {
+// CHECK:STDOUT:     .foo = <poisoned>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Cpp = imports.%Cpp
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Cpp.import_cpp = import_cpp {
+// CHECK:STDOUT:     import Cpp "non_function_decl.h"
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Cpp.ref: <namespace> = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp]
+// CHECK:STDOUT:   %foo.ref: <error> = name_ref foo, <error> [concrete = <error>]
+// CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -318,6 +318,7 @@ CARBON_DIAGNOSTIC_KIND(QualifiedDeclInUndefinedInterfaceScope)
 
 // Name lookup.
 CARBON_DIAGNOSTIC_KIND(FromExtendHere)
+CARBON_DIAGNOSTIC_KIND(InCppNameLookup)
 CARBON_DIAGNOSTIC_KIND(InNameLookup)
 CARBON_DIAGNOSTIC_KIND(NameAmbiguousDueToExtend)
 CARBON_DIAGNOSTIC_KIND(NameNotFound)

+ 3 - 1
toolchain/driver/compile_subcommand.cpp

@@ -412,6 +412,7 @@ class CompilationUnit {
   std::optional<std::function<auto()->const Parse::TreeAndSubtrees&>>
       tree_and_subtrees_getter_;
   std::optional<SemIR::File> sem_ir_;
+  std::unique_ptr<clang::ASTUnit> cpp_ast_;
   std::unique_ptr<llvm::LLVMContext> llvm_context_;
   std::unique_ptr<llvm::Module> module_;
 };
@@ -509,7 +510,8 @@ auto CompilationUnit::GetCheckUnit(SemIR::CheckIRId check_ir_id)
           .value_stores = &value_stores_,
           .timings = timings_ ? &*timings_ : nullptr,
           .tree_and_subtrees_getter = *tree_and_subtrees_getter_,
-          .sem_ir = &*sem_ir_};
+          .sem_ir = &*sem_ir_,
+          .cpp_ast = &cpp_ast_};
 }
 
 auto CompilationUnit::PostCheck() -> void {

+ 1 - 0
toolchain/sem_ir/BUILD

@@ -126,6 +126,7 @@ cc_library(
         "//toolchain/lex:token_kind",
         "//toolchain/parse:node_kind",
         "//toolchain/parse:tree",
+        "@llvm-project//clang:frontend",
         "@llvm-project//llvm:Support",
     ],
 )

+ 9 - 0
toolchain/sem_ir/file.h

@@ -5,6 +5,7 @@
 #ifndef CARBON_TOOLCHAIN_SEM_IR_FILE_H_
 #define CARBON_TOOLCHAIN_SEM_IR_FILE_H_
 
+#include "clang/Frontend/ASTUnit.h"
 #include "common/error.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/iterator_range.h"
@@ -186,6 +187,10 @@ class File : public Printable<File> {
   auto import_cpps() const -> const ValueStore<ImportCppId>& {
     return import_cpps_;
   }
+  auto cpp_ast() -> clang::ASTUnit* { return cpp_ast_; }
+  // TODO: When the AST can be created before creating `File`, initialize the
+  // pointer in the constructor and remove this function.
+  auto set_cpp_ast(clang::ASTUnit* cpp_ast) -> void { cpp_ast_ = cpp_ast; }
   auto names() const -> NameStoreWrapper {
     return NameStoreWrapper(&identifiers());
   }
@@ -301,6 +306,10 @@ class File : public Printable<File> {
   // List of Cpp imports.
   ValueStore<ImportCppId> import_cpps_;
 
+  // The Clang AST to use when looking up `Cpp` names. Null if there are no
+  // `Cpp` imports.
+  clang::ASTUnit* cpp_ast_ = nullptr;
+
   // Type blocks within the IR. These reference entries in types_. Storage for
   // the data is provided by allocator_.
   BlockValueStore<TypeBlockId> type_blocks_;

+ 9 - 0
toolchain/sem_ir/name_scope.h

@@ -212,6 +212,12 @@ class NameScope : public Printable<NameScope> {
     is_closed_import_ = is_closed_import;
   }
 
+  auto is_cpp_scope() const -> bool { return is_cpp_scope_; }
+
+  auto set_is_cpp_scope(bool is_cpp_scope) -> void {
+    is_cpp_scope_ = is_cpp_scope;
+  }
+
   // Returns true if this name scope describes an imported package.
   auto is_imported_package() const -> bool {
     return is_closed_import() && parent_scope_id() == NameScopeId::Package;
@@ -272,6 +278,9 @@ class NameScope : public Printable<NameScope> {
   // True if this is a closed namespace created by importing a package.
   bool is_closed_import_ = false;
 
+  // True if this is the `Cpp` namescope used when importing C++ code.
+  bool is_cpp_scope_ = false;
+
   // Imported IR scopes that compose this namespace. This will be empty for
   // scopes that correspond to the current package.
   llvm::SmallVector<std::pair<ImportIRId, NameScopeId>, 0> import_ir_scopes_;