mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:40:11 +01:00
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>
This commit is contained in:
co-authored by
Richard Smith
parent
1e9708e2cc
commit
4df2b6ea9d
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -25,6 +25,7 @@ enum class CoreInterface : std::int8_t {
|
||||
Copy = 1 << 0,
|
||||
Destroy = 1 << 1,
|
||||
IntFitsIn = 1 << 2,
|
||||
CppUnsafeDeref = 1 << 3,
|
||||
|
||||
Unknown = -1,
|
||||
};
|
||||
|
||||
@@ -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,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)();
|
||||
}
|
||||
Reference in New Issue
Block a user