Ver Fonte

Support round-tripping entities through C++ and Carbon. (#7022)

Use the same C++ -> Carbon map for both interop directions, and when
importing an entity from Carbon -> C++, check whether it was originally
a C++ entity and if so return the original.

Assisted-by: Gemini via Google Antigravity
Richard Smith há 3 semanas atrás
pai
commit
cc4fd39238

+ 60 - 19
toolchain/check/cpp/generate_ast.cpp

@@ -8,6 +8,7 @@
 #include <string>
 
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
 #include "clang/Basic/FileManager.h"
 #include "clang/CodeGen/ModuleBuilder.h"
 #include "clang/Frontend/CompilerInstance.h"
@@ -340,12 +341,6 @@ class CarbonExternalASTSource : public clang::ExternalASTSource {
 
   Check::Context* context_;
   clang::ASTContext* ast_context_;
-  // The association between clang DeclContexts and the corresponding
-  // SemIR::Namespaces in Carbon.
-  // TODO: reuse the SemIR::File::ClangDeclStore to avoid duplicates, and to
-  // enable roundtripping through forward and reverse interop (once we have
-  // syntax/support for that).
-  Map<clang::DeclContext*, SemIR::InstId> scope_map_;
 
   // Has the "Carbon" C++ namespace been created yet
   // (this could be replaced with `!scope_map_.empty()` if Carbon::Map supported
@@ -363,6 +358,21 @@ void CarbonExternalASTSource::StartTranslationUnit(
   translation_unit.setHasExternalVisibleStorage();
 }
 
+// If the given name scope was produced by importing a C++ declaration, return
+// the corresponding Clang declaration.
+static auto GetClangDeclForScope(Context& context, SemIR::NameScopeId scope_id)
+    -> clang::NamedDecl* {
+  if (!scope_id.has_value()) {
+    return nullptr;
+  }
+  auto& scope = context.name_scopes().Get(scope_id);
+  if (!scope.is_cpp_scope()) {
+    return nullptr;
+  }
+  return dyn_cast<clang::NamedDecl>(
+      context.clang_decls().Get(scope.clang_decl_context_id()).key.decl);
+}
+
 auto CarbonExternalASTSource::MapInstIdToClangDecl(
     clang::DeclContext& decl_context, LookupResult lookup)
     -> clang::NamedDecl* {
@@ -372,10 +382,21 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
   auto target_inst = context_->insts().Get(target_constant);
   CARBON_KIND_SWITCH(target_inst) {
     case CARBON_KIND(SemIR::Namespace namespace_info): {
+      if (auto* decl =
+              GetClangDeclForScope(*context_, namespace_info.name_scope_id)) {
+        return decl;
+      }
       auto& name_scope =
           context_->name_scopes().Get(namespace_info.name_scope_id);
       auto* identifier_info =
           GetClangIdentifierInfo(*context_, name_scope.name_id());
+      if (!identifier_info) {
+        // TODO: Handle keyword package names like `Cpp` and `Core`. These can
+        // be named from C++ via an alias.
+        context_->TODO(SemIR::LocId(target_inst_id),
+                       "interop with non-identifier package name");
+        return nullptr;
+      }
       // TODO: Don't immediately use the decl_context - build any intermediate
       // namespaces iteratively.
       // Eventually add a mapping and use that/populate it/keep it up to date.
@@ -384,19 +405,26 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
       auto* namespace_decl = clang::NamespaceDecl::Create(
           *ast_context_, &decl_context, false, clang::SourceLocation(),
           clang::SourceLocation(), identifier_info, nullptr, false);
-      auto result = scope_map_.Insert(namespace_decl->getPrimaryContext(),
-                                      target_inst_id);
-      CARBON_CHECK(result.is_inserted(), "Inserting over an existing entry.");
+      auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(namespace_decl);
+      context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
       namespace_decl->setHasExternalVisibleStorage();
       return namespace_decl;
     }
     case CARBON_KIND(SemIR::ClassType class_type): {
       const auto& class_info = context_->classes().Get(class_type.class_id);
+      if (auto* decl = GetClangDeclForScope(*context_, class_info.scope_id)) {
+        return decl;
+      }
       auto* identifier_info =
           GetClangIdentifierInfo(*context_, class_info.name_id);
-      return clang::CXXRecordDecl::Create(
+      CARBON_CHECK(identifier_info, "class with non-identifier name {0}",
+                   class_info.name_id);
+      auto* record_decl = clang::CXXRecordDecl::Create(
           *ast_context_, clang::TagTypeKind::Class, &decl_context,
           clang::SourceLocation(), clang::SourceLocation(), identifier_info);
+      auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(record_decl);
+      context_->clang_decls().Add({.key = key, .inst_id = target_inst_id});
+      return record_decl;
     }
     case SemIR::StructValue::Kind: {
       auto callee = GetCallee(context_->sem_ir(), target_constant);
@@ -406,8 +434,14 @@ auto CarbonExternalASTSource::MapInstIdToClangDecl(
       }
       const SemIR::Function& function =
           context_->functions().Get(callee_function->function_id);
+      if (function.clang_decl_id.has_value()) {
+        return cast<clang::NamedDecl>(
+            context_->clang_decls().Get(function.clang_decl_id).key.decl);
+      }
       auto* identifier_info =
           GetClangIdentifierInfo(*context_, function.name_id);
+      CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
+                   function.name_id);
 
       if (function.call_param_ranges.explicit_size() != 0) {
         context_->TODO(target_inst_id,
@@ -480,20 +514,24 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
         clang::SourceLocation(), &ast_context.Idents.get(carbon_namespace_name),
         nullptr, false);
     carbon_cpp_namespace->setHasExternalVisibleStorage();
-    auto result = scope_map_.Insert(carbon_cpp_namespace->getPrimaryContext(),
-                                    SemIR::Namespace::PackageInstId);
-    CARBON_CHECK(result.is_inserted(), "Inserting over an existing entry.");
+    auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
+    context_->clang_decls().Add(
+        {.key = key, .inst_id = SemIR::Namespace::PackageInstId});
     SetExternalVisibleDeclsForName(decl_context, decl_name,
                                    {carbon_cpp_namespace});
     root_scope_initialized_ = true;
     return true;
   }
 
-  auto decl_context_inst_id =
-      scope_map_.Lookup(decl_context->getPrimaryContext());
+  // Find the Carbon declaration corresponding to this Clang declaration.
+  auto* decl = cast<clang::Decl>(
+      const_cast<clang::DeclContext*>(decl_context->getPrimaryContext()));
+  auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(decl);
+  auto decl_id = context_->clang_decls().Lookup(key);
   CARBON_CHECK(
-      decl_context_inst_id,
+      decl_id.has_value(),
       "The DeclContext should already be associated with a Carbon InstId.");
+  auto decl_context_inst_id = context_->clang_decls().Get(decl_id).inst_id;
 
   llvm::SmallVector<Check::LookupScope> lookup_scopes;
 
@@ -501,7 +539,7 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
   // here - completeness should've been checked by clang before this point.
   if (!AppendLookupScopesForConstant(
           *context_, SemIR::LocId::None,
-          context_->constant_values().Get(decl_context_inst_id.value()),
+          context_->constant_values().Get(decl_context_inst_id),
           SemIR::ConstantId::None, &lookup_scopes)) {
     return false;
   }
@@ -524,8 +562,11 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
   }
 
   // Map the found Carbon entity to a Clang NamedDecl.
-  // Use the key to reach the owned, mutable copy of decl_context.
-  auto* clang_decl = MapInstIdToClangDecl(*decl_context_inst_id.key(), result);
+  // TODO: Stop passing in the `DeclContext` here; the context in which we
+  // performed the lookup that first found the Carbon declaration should not
+  // affect the Clang declaration we produce.
+  auto* clang_decl = MapInstIdToClangDecl(
+      *const_cast<clang::DeclContext*>(decl_context), result);
   if (!clang_decl) {
     return false;
   }

+ 43 - 0
toolchain/check/testdata/interop/cpp/class/roundtrip.carbon

@@ -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-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/class/roundtrip.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/class/roundtrip.carbon
+
+// --- carbon_to_cpp_to_carbon.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+class C {}
+
+inline Cpp '''
+using CAlias = Carbon::C;
+''';
+
+fn F(p: C*) {
+  let unused q: Cpp.CAlias* = p;
+}
+
+// --- cpp_to_carbon_to_cpp.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+inline Cpp '''
+class C {};
+''';
+
+alias CAlias = Cpp.C;
+
+inline Cpp '''
+auto F(C *p) -> Carbon::CAlias* {
+  return p;
+}
+''';

+ 2 - 2
toolchain/check/testdata/interop/cpp/namespace.carbon → toolchain/check/testdata/interop/cpp/namespace/basic.carbon

@@ -6,9 +6,9 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/basic.carbon
 
 // ============================================================================
 // Single

+ 100 - 0
toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon

@@ -0,0 +1,100 @@
+// 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/namespace/roundtrip.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/namespace/roundtrip.carbon
+
+// --- carbon_to_cpp_to_carbon.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+namespace N;
+
+inline Cpp '''
+namespace M = Carbon::N;
+''';
+
+class C {}
+
+fn N.F(c: C);
+
+fn G(c: C) {
+  Cpp.M.F(c);
+}
+
+// --- cpp_to_carbon_to_cpp.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+inline Cpp '''
+namespace N {}
+''';
+
+alias M = Cpp.N;
+
+inline Cpp '''
+namespace N { void F(); }
+
+static_assert(&N::F == &Carbon::M::F);
+''';
+
+// --- cpp_carbon.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+fn F();
+
+fn G() {
+  // TODO: Should we disallow this?
+  Cpp.Carbon.F();
+}
+
+// --- fail_carbon_cpp.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+inline Cpp '''
+void F();
+
+void G() {
+  // This is not found because it looks for the identifier `Cpp`, not the keyword `Cpp`.
+  // CHECK:STDERR: fail_carbon_cpp.carbon:[[@LINE+4]]:11: error: no member named 'Cpp' in namespace 'Carbon' [CppInteropParseError]
+  // CHECK:STDERR:    14 |   Carbon::Cpp::F();
+  // CHECK:STDERR:       |   ~~~~~~~~^
+  // CHECK:STDERR:
+  Carbon::Cpp::F();
+}
+''';
+
+// --- fail_carbon_raw_cpp.carbon
+library "[[@TEST_NAME]]";
+
+import Cpp;
+
+// CHECK:STDERR: fail_carbon_raw_cpp.carbon:[[@LINE+4]]:7: error: semantics TODO: `interop with non-identifier package name` [SemanticsTodo]
+// CHECK:STDERR: alias r#Cpp = Cpp;
+// CHECK:STDERR:       ^~~
+// CHECK:STDERR:
+alias r#Cpp = Cpp;
+
+inline Cpp '''
+void F();
+
+void G() {
+  // CHECK:STDERR: fail_carbon_raw_cpp.carbon:[[@LINE+4]]:11: error: no member named 'Cpp' in namespace 'Carbon' [CppInteropParseError]
+  // CHECK:STDERR:    19 |   Carbon::Cpp::F();
+  // CHECK:STDERR:       |   ~~~~~~~~^
+  // CHECK:STDERR:
+  Carbon::Cpp::F();
+}
+''';