diff --git a/core/prelude/operators.carbon b/core/prelude/operators.carbon index d6d9336da0bc..6c6153068b15 100644 --- a/core/prelude/operators.carbon +++ b/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"; diff --git a/core/prelude/operators/deref.carbon b/core/prelude/operators/deref.carbon new file mode 100644 index 000000000000..c9c6ecbba1ab --- /dev/null +++ b/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; +} diff --git a/toolchain/check/core_identifier.def b/toolchain/check/core_identifier.def index 5f9ce2351d83..8ddb8893a165 100644 --- a/toolchain/check/core_identifier.def +++ b/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) diff --git a/toolchain/check/cpp/impl_lookup.cpp b/toolchain/check/cpp/impl_lookup.cpp index ea5327362858..1e102b9f726e 100644 --- a/toolchain/check/cpp/impl_lookup.cpp +++ b/toolchain/check/cpp/impl_lookup.cpp @@ -66,6 +66,9 @@ enum class AssociatedFunction : std::underlying_type_t { // 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 { + 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(decl_info.decl); + auto* cpp_fn = cast(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( diff --git a/toolchain/check/custom_witness.cpp b/toolchain/check/custom_witness.cpp index 187f3390c72e..c8e0f6fc3eef 100644 --- a/toolchain/check/custom_witness.cpp +++ b/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. diff --git a/toolchain/check/custom_witness.h b/toolchain/check/custom_witness.h index a516b69e1ca0..7b905c81e678 100644 --- a/toolchain/check/custom_witness.h +++ b/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, }; diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index cd9425448444..2a1525b9908b 100644 --- a/toolchain/check/import_ref.cpp +++ b/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( + 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); } diff --git a/toolchain/check/testdata/interop/cpp/deref.carbon b/toolchain/check/testdata/interop/cpp/deref.carbon new file mode 100644 index 000000000000..e6d535e482a2 --- /dev/null +++ b/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 +concept SameImpl = __is_same(T, U); + +template +concept SameAs = SameImpl && SameImpl; + +template +class MaybeDeref { +public: + auto operator*() const -> int& + requires SameAs; + + 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)(); +}