Quellcode durchsuchen

adds checking support for `CppUnsafeDeref` witness (#6890)

Iterators, smart pointers, optional, and expected types depend on
`operator*`. This commit adds `CppUnsafeDeref` as a core interface, with
an associated function, so that the compiler can dereference
user-defined C++ types.

Things not implemented in this commit:

* `operator*` overload resolution
* SemIR lowering

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Christopher Di Bella vor 1 Monat
Ursprung
Commit
4df2b6ea9d

+ 1 - 0
core/prelude/operators.carbon

@@ -9,3 +9,4 @@ export import library "prelude/operators/as";
 export import library "prelude/operators/bitwise";
 export import library "prelude/operators/comparison";
 export import library "prelude/operators/index";
+export import library "prelude/operators/deref";

+ 11 - 0
core/prelude/operators/deref.carbon

@@ -0,0 +1,11 @@
+// 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
+
+package Core library "prelude/operators/deref";
+
+// TODO: Align with https://docs.carbon-lang.dev/docs/design/values.html#dereferencing-customization.
+interface CppUnsafeDeref {
+  let Result:! type;
+  fn Op[self: Self]() -> ref Result;
+}

+ 1 - 0
toolchain/check/core_identifier.def

@@ -33,6 +33,7 @@ CARBON_CORE_IDENTIFIER(Char)
 CARBON_CORE_IDENTIFIER(Convert)
 CARBON_CORE_IDENTIFIER(Copy)
 CARBON_CORE_IDENTIFIER(CppCompat)
+CARBON_CORE_IDENTIFIER(CppUnsafeDeref)
 CARBON_CORE_IDENTIFIER(Dec)
 CARBON_CORE_IDENTIFIER(Destroy)
 CARBON_CORE_IDENTIFIER(DivAssignWith)

+ 46 - 15
toolchain/check/cpp/impl_lookup.cpp

@@ -66,6 +66,9 @@ enum class AssociatedFunction : std::underlying_type_t<CoreInterface> {
 
   // CoreInterface::Destroy
   Destructor = llvm::to_underlying(CoreInterface::Destroy),
+
+  // CoreInterface::CppUnsafeDeref
+  CppUnsafeDeref = llvm::to_underlying(CoreInterface::CppUnsafeDeref),
 };
 
 // Maps a `CoreInterface` to its corresponding set of `CppCoreFunction`s.
@@ -76,6 +79,8 @@ static auto GetCppAssociatedFunctions(const CoreInterface core_interface)
       return {llvm::to_underlying(AssociatedFunction::CopyConstructor)};
     case CoreInterface::Destroy:
       return {llvm::to_underlying(AssociatedFunction::Destructor)};
+    case CoreInterface::CppUnsafeDeref:
+      return {llvm::to_underlying(AssociatedFunction::CppUnsafeDeref)};
     case CoreInterface::Unknown:
     case CoreInterface::IntFitsIn:
       CARBON_FATAL("No AssociatedFunction mapping for this interface");
@@ -83,20 +88,41 @@ static auto GetCppAssociatedFunctions(const CoreInterface core_interface)
 }
 
 // Retrieves a `core_interface`'s corresponding `NamedDecl`, also with the
-// expected number of parameters. May return a null decl.
-auto GetDeclForCoreInterface(clang::Sema& clang_sema,
+// expected number of parameters. May return a null decl to indicate nothing was
+// found, or nullopt to indicate `SemIR::ErrInst::InstId` should be propagated.
+auto GetDeclForCoreInterface(Context& context, SemIR::LocId loc_id,
                              AssociatedFunction associated_function,
-                             clang::CXXRecordDecl* class_decl) -> DeclInfo {
+                             clang::CXXRecordDecl* class_decl)
+    -> std::optional<DeclInfo> {
+  auto& clang_sema = context.clang_sema();
+  CARBON_CHECK(class_decl != nullptr);
+
   // TODO: Handle other interfaces.
 
   switch (associated_function) {
     case AssociatedFunction::CopyConstructor:
-      return {.decl = clang_sema.LookupCopyingConstructor(
-                  class_decl, clang::Qualifiers::Const),
-              .signature = {.num_params = 1}};
+      return DeclInfo{.decl = clang_sema.LookupCopyingConstructor(
+                          class_decl, clang::Qualifiers::Const),
+                      .signature = {.num_params = 1}};
     case AssociatedFunction::Destructor:
-      return {.decl = clang_sema.LookupDestructor(class_decl),
-              .signature = {.num_params = 0}};
+      return DeclInfo{.decl = clang_sema.LookupDestructor(class_decl),
+                      .signature = {.num_params = 0}};
+    case AssociatedFunction::CppUnsafeDeref: {
+      auto candidates = class_decl->lookup(
+          clang_sema.getASTContext().DeclarationNames.getCXXOperatorName(
+              clang::OO_Star));
+      if (candidates.empty()) {
+        return DeclInfo{};
+      }
+
+      if (!candidates.isSingleResult()) {
+        context.TODO(loc_id, "operator* overload sets not implemented yet");
+        return std::nullopt;
+      }
+
+      return DeclInfo{.decl = *candidates.begin(),
+                      .signature = {.num_params = 0}};
+    }
   }
 }
 
@@ -106,14 +132,18 @@ static auto FindCppAssociatedFunction(Context& context, SemIR::LocId loc_id,
     -> SemIR::InstId {
   // TODO: This should provide `Destroy` for enums and other trivially
   // destructible types.
-  auto decl_info = GetDeclForCoreInterface(context.clang_sema(),
-                                           associated_function, class_decl);
-  if (!decl_info.decl) {
+  auto decl_info =
+      GetDeclForCoreInterface(context, loc_id, associated_function, class_decl);
+  if (!decl_info.has_value()) {
+    return SemIR::ErrorInst::InstId;
+  }
+
+  if (!decl_info->decl) {
     // TODO: If the impl lookup failure is an error, we should produce a
-    // diagnostic explaining why the class is not copyable/destructible.
+    // diagnostic explaining why the class does not satisfy the core interface.
     return SemIR::InstId::None;
   }
-  auto* cpp_fn = cast<clang::FunctionDecl>(decl_info.decl);
+  auto* cpp_fn = cast<clang::FunctionDecl>(decl_info->decl);
 
   if (context.clang_sema().DiagnoseUseOfOverloadedDecl(
           cpp_fn, GetCppLocation(context, loc_id))) {
@@ -121,7 +151,7 @@ static auto FindCppAssociatedFunction(Context& context, SemIR::LocId loc_id,
   }
 
   auto fn_id =
-      ImportCppFunctionDecl(context, loc_id, cpp_fn, decl_info.signature);
+      ImportCppFunctionDecl(context, loc_id, cpp_fn, decl_info->signature);
   if (fn_id == SemIR::ErrorInst::InstId) {
     return SemIR::ErrorInst::InstId;
   }
@@ -147,7 +177,8 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
 
   switch (core_interface) {
     case CoreInterface::Copy:
-    case CoreInterface::Destroy: {
+    case CoreInterface::Destroy:
+    case CoreInterface::CppUnsafeDeref: {
       auto associated_functions = GetCppAssociatedFunctions(core_interface);
       CARBON_CHECK(associated_functions.count() == 1);
       witness_id = FindCppAssociatedFunction(

+ 8 - 4
toolchain/check/custom_witness.cpp

@@ -282,10 +282,13 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
     return CoreInterface::Unknown;
   }
 
-  for (auto [core_identifier, core_interface] :
-       {std::pair{CoreIdentifier::Copy, CoreInterface::Copy},
-        std::pair{CoreIdentifier::Destroy, CoreInterface::Destroy},
-        std::pair{CoreIdentifier::IntFitsIn, CoreInterface::IntFitsIn}}) {
+  constexpr auto CoreIdentifiersToInterfaces = std::array{
+      std::pair{CoreIdentifier::Copy, CoreInterface::Copy},
+      std::pair{CoreIdentifier::Destroy, CoreInterface::Destroy},
+      std::pair{CoreIdentifier::IntFitsIn, CoreInterface::IntFitsIn},
+      std::pair{CoreIdentifier::CppUnsafeDeref, CoreInterface::CppUnsafeDeref}};
+
+  for (auto [core_identifier, core_interface] : CoreIdentifiersToInterfaces) {
     if (interface.name_id ==
         context.core_identifiers().AddNameId(core_identifier)) {
       return core_interface;
@@ -462,6 +465,7 @@ auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
     case CoreInterface::IntFitsIn:
       return MakeIntFitsInWitness(context, loc_id, query_self_const_id,
                                   query_specific_interface_id);
+    case CoreInterface::CppUnsafeDeref:
     case CoreInterface::Copy:
     case CoreInterface::Unknown:
       // TODO: Handle more interfaces, particularly copy, move, and conversion.

+ 1 - 0
toolchain/check/custom_witness.h

@@ -25,6 +25,7 @@ enum class CoreInterface : std::int8_t {
   Copy = 1 << 0,
   Destroy = 1 << 1,
   IntFitsIn = 1 << 2,
+  CppUnsafeDeref = 1 << 3,
 
   Unknown = -1,
 };

+ 18 - 0
toolchain/check/import_ref.cpp

@@ -3671,6 +3671,21 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
       resolver, {.type_id = SemIR::TypeType::TypeId, .pointee_id = pointee_id});
 }
 
+static auto TryResolveTypedInst(ImportRefResolver& resolver,
+                                SemIR::RefForm inst) -> ResolveResult {
+  auto type_component_inst_id =
+      GetLocalConstantId(resolver, inst.type_component_inst_id);
+  if (resolver.HasNewWork()) {
+    return ResolveResult::Retry();
+  }
+
+  return ResolveResult::Deduplicated<SemIR::RefForm>(
+      resolver, {.type_id = SemIR::TypeType::TypeId,
+                 .type_component_inst_id = resolver.local_types().GetTypeInstId(
+                     resolver.local_types().GetTypeIdForTypeConstantId(
+                         type_component_inst_id))});
+}
+
 static auto TryResolveTypedInst(ImportRefResolver& resolver,
                                 SemIR::RequireCompleteType inst)
     -> ResolveResult {
@@ -4180,6 +4195,9 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver,
     case CARBON_KIND(SemIR::RefBindingPattern inst): {
       return TryResolveTypedInst(resolver, inst, constant_inst_id);
     }
+    case CARBON_KIND(SemIR::RefForm inst): {
+      return TryResolveTypedInst(resolver, inst);
+    }
     case CARBON_KIND(SemIR::RefParamPattern inst): {
       return TryResolveTypedInst(resolver, inst, constant_inst_id);
     }

+ 119 - 0
toolchain/check/testdata/interop/cpp/deref.carbon

@@ -0,0 +1,119 @@
+// 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/full.carbon
+//
+// EXTRA-ARGS: --target=aarch64-unknown-linux --clang-arg=--std=c++20
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/deref.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/deref.carbon
+
+// --- fail_todo_const_deref.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+class ConstDeref {
+public:
+  auto operator*() const -> int&;
+};
+''';
+
+fn TestDerefPass(var int_ptr: Cpp.ConstDeref) -> ref i32 {
+  // CHECK:STDERR: fail_todo_const_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `Unsupported definition of interface CppUnsafeDeref` [SemanticsTodo]
+  // CHECK:STDERR:   return int_ptr.(Core.CppUnsafeDeref.Op)();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  return int_ptr.(Core.CppUnsafeDeref.Op)();
+}
+
+// --- fail_todo_mutable_deref.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+class MutableDeref {
+public:
+  auto operator*() -> int&;
+};
+''';
+
+fn TestDerefPass(var int_ptr: Cpp.MutableDeref) -> ref i32 {
+  // CHECK:STDERR: fail_todo_mutable_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `Unsupported definition of interface CppUnsafeDeref` [SemanticsTodo]
+  // CHECK:STDERR:   return int_ptr.(Core.CppUnsafeDeref.Op)();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  return int_ptr.(Core.CppUnsafeDeref.Op)();
+}
+
+// --- fail_todo_multi_deref.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+class MultiDeref {
+public:
+  auto operator*() & -> int&;
+  auto operator*() const& -> const int&;
+
+  auto operator*() && -> int&&;
+  auto operator*() const&& -> int const&&;
+};
+''';
+
+fn TestDerefPass(var int_ptr: Cpp.MultiDeref) -> ref i32 {
+  // CHECK:STDERR: fail_todo_multi_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `operator* overload sets not implemented yet` [SemanticsTodo]
+  // CHECK:STDERR:   return int_ptr.(Core.CppUnsafeDeref.Op)();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  return int_ptr.(Core.CppUnsafeDeref.Op)();
+}
+
+// --- fail_todo_maybe_deref.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+template<class T, class U>
+concept SameImpl = __is_same(T, U);
+
+template<class T, class U>
+concept SameAs = SameImpl<T, U> && SameImpl<U, T>;
+
+template<class T>
+class MaybeDeref {
+public:
+  auto operator*() const -> int&
+  requires SameAs<T, int>;
+
+  auto operator*() const -> const T&;
+};
+''';
+
+fn TestDerefPass(var int_ptr: Cpp.MaybeDeref(i32)) -> ref i32 {
+  // CHECK:STDERR: fail_todo_maybe_deref.carbon:[[@LINE+4]]:10: error: semantics TODO: `operator* overload sets not implemented yet` [SemanticsTodo]
+  // CHECK:STDERR:   return int_ptr.(Core.CppUnsafeDeref.Op)();
+  // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  return int_ptr.(Core.CppUnsafeDeref.Op)();
+}
+
+// --- fail_not_a_ptr.carbon
+
+library "[[@TEST_NAME]]";
+
+import Cpp inline '''c++
+class NotAPtr {};
+''';
+
+fn TestDerefFail(not_ptr: Cpp.NotAPtr) {
+  // CHECK:STDERR: fail_not_a_ptr.carbon:[[@LINE+4]]:3: error: cannot access member of interface `Core.CppUnsafeDeref` in type `Cpp.NotAPtr` that does not implement that interface [MissingImplInMemberAccess]
+  // CHECK:STDERR:   not_ptr.(Core.CppUnsafeDeref.Op)();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  not_ptr.(Core.CppUnsafeDeref.Op)();
+}