mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 14:51:03 +01:00
Add support for using C++ user-defined conversions via interop (#6646)
When performing an implicit conversion to or from a C++ class type, look for a C++ implicit conversion, and if that conversion involves a function call (to a constructor or conversion function), call that function to perform the conversion. Note that this is just a first pass at supporting implicit conversions. There are a lot of other things that can happen in a C++ implicit conversion, such as aggregate initialization or `std::initializer_list` initialization that aren't handled here. In addition, we intentionally leave all standard conversions to Carbon to perform, so that we will reject conversions such as `i32 -> unsigned` that C++ would select but Carbon considers to be invalid. Also support `as` conversions. These are treated analogously, but perform direct-initialization instead of copy-initialization, so they also find `explicit` constructors and conversion functions. In order to give good diagnostics, also track the original C++ source location for imported C++ functions on the imported version of the function. Assisted-by: Gemini 3 Pro via Antigravity
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "toolchain/check/cpp/operators.h"
|
||||
|
||||
#include "clang/Sema/Initialization.h"
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "toolchain/check/core_identifier.h"
|
||||
@@ -192,6 +193,114 @@ static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
}
|
||||
|
||||
static auto LookupCppConversion(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::InstId source_id,
|
||||
SemIR::TypeId dest_type_id, bool allow_explicit)
|
||||
-> SemIR::InstId {
|
||||
auto dest_type = MapToCppType(context, dest_type_id);
|
||||
if (dest_type.isNull()) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
auto* arg_expr = InventClangArg(context, source_id);
|
||||
// If we can't map the argument, we can't perform the conversion.
|
||||
if (!arg_expr) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
auto loc = GetCppLocation(context, loc_id);
|
||||
|
||||
// Form a Clang initialization sequence.
|
||||
auto& sema = context.clang_sema();
|
||||
clang::InitializedEntity entity =
|
||||
clang::InitializedEntity::InitializeTemporary(dest_type);
|
||||
clang::InitializationKind kind =
|
||||
allow_explicit ? clang::InitializationKind::CreateDirect(
|
||||
loc, /*LParenLoc=*/clang::SourceLocation(),
|
||||
/*RParenLoc=*/clang::SourceLocation())
|
||||
: clang::InitializationKind::CreateCopy(
|
||||
loc, /*EqualLoc=*/clang::SourceLocation());
|
||||
clang::MultiExprArg args(arg_expr);
|
||||
// `(a, b) as T` uses `T{a, b}`, not `T({a, b})`. The latter would introduce
|
||||
// a redundant extra copy.
|
||||
// TODO: We need to communicate this back to the caller so they know to call
|
||||
// the constructor with an exploded argument list somehow.
|
||||
if (allow_explicit && isa<clang::InitListExpr>(arg_expr)) {
|
||||
kind = clang::InitializationKind::CreateDirectList(loc);
|
||||
}
|
||||
clang::InitializationSequence init(sema, entity, kind, args);
|
||||
|
||||
if (init.Failed()) {
|
||||
// TODO: Are there initialization failures that we should translate into
|
||||
// errors rather than a missing conversion?
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
// Scan the steps looking for user-defined conversions. For now we just find
|
||||
// and return the first such conversion function. We skip over standard
|
||||
// conversions; we'll perform those using the Carbon rules as part of calling
|
||||
// the C++ conversion function.
|
||||
for (const auto& step : init.steps()) {
|
||||
switch (step.Kind) {
|
||||
case clang::InitializationSequence::SK_UserConversion:
|
||||
case clang::InitializationSequence::SK_ConstructorInitialization: {
|
||||
if (auto* ctor =
|
||||
dyn_cast<clang::CXXConstructorDecl>(step.Function.Function);
|
||||
ctor && ctor->isCopyOrMoveConstructor()) {
|
||||
// Skip copy / move constructor calls. They shouldn't be performed
|
||||
// this way because they're not considered conversions in Carbon, and
|
||||
// will frequently lead to infinite recursion because we'll end up
|
||||
// back here when attempting to convert the argument.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sema.DiagnoseUseOfOverloadedDecl(step.Function.Function, loc)) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
sema.MarkFunctionReferenced(loc, step.Function.Function);
|
||||
|
||||
auto result_id = ImportCppFunctionDecl(
|
||||
context, loc_id, step.Function.Function,
|
||||
// If this is a constructor, the source is passed as an argument;
|
||||
// otherwise, this is a conversion function and the source is passed
|
||||
// as `self`.
|
||||
isa<clang::CXXConstructorDecl>(step.Function.Function) ? 1 : 0);
|
||||
if (auto fn_decl = context.insts().TryGetAsWithId<SemIR::FunctionDecl>(
|
||||
result_id)) {
|
||||
CheckCppOverloadAccess(context, loc_id, step.Function.FoundDecl,
|
||||
fn_decl->inst_id);
|
||||
} else {
|
||||
CARBON_CHECK(result_id == SemIR::ErrorInst::InstId);
|
||||
}
|
||||
|
||||
// TODO: There may be other conversions later in the sequence that we
|
||||
// need to model; we've only applied the first one here.
|
||||
return result_id;
|
||||
}
|
||||
|
||||
case clang::InitializationSequence::SK_ConversionSequence:
|
||||
case clang::InitializationSequence::SK_ConversionSequenceNoNarrowing: {
|
||||
// Implicit conversions are handled by the normal Carbon conversion
|
||||
// logic, so we ignore them here.
|
||||
continue;
|
||||
}
|
||||
|
||||
default: {
|
||||
// TODO: Handle other kinds of initialization steps. For now we assume
|
||||
// they will be handled by our function call logic and we can skip them.
|
||||
RawStringOstream os;
|
||||
os << "Unsupported initialization sequence:\n";
|
||||
init.dump(os);
|
||||
context.TODO(loc_id, os.TakeStr());
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
|
||||
// Register an annotation scope to flush any Clang diagnostics when we return.
|
||||
@@ -200,6 +309,26 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
Diagnostics::AnnotationScope annotate_diagnostics(&context.emitter(),
|
||||
[](auto& /*builder*/) {});
|
||||
|
||||
// Handle `ImplicitAs` and `As`.
|
||||
if (op.interface_name == CoreIdentifier::ImplicitAs ||
|
||||
op.interface_name == CoreIdentifier::As) {
|
||||
if (op.interface_args_ref.size() != 1 || arg_ids.size() != 1) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
// The argument is the destination type for both interfaces.
|
||||
auto dest_const_id =
|
||||
context.constant_values().Get(op.interface_args_ref[0]);
|
||||
auto dest_type_id =
|
||||
context.types().TryGetTypeIdForTypeConstantId(dest_const_id);
|
||||
if (!dest_type_id.has_value()) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
return LookupCppConversion(
|
||||
context, loc_id, arg_ids[0], dest_type_id,
|
||||
/*allow_explicit=*/op.interface_name == CoreIdentifier::As);
|
||||
}
|
||||
|
||||
auto op_kind =
|
||||
GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
|
||||
if (!op_kind) {
|
||||
@@ -304,7 +433,9 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
|
||||
auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool {
|
||||
auto* clang_method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
|
||||
return clang_method_decl && clang_method_decl->isOverloadedOperator();
|
||||
return clang_method_decl &&
|
||||
(clang_method_decl->isOverloadedOperator() ||
|
||||
isa<clang::CXXConversionDecl>(clang_method_decl));
|
||||
}
|
||||
|
||||
static auto GetAsCppFunctionDecl(Context& context, SemIR::InstId inst_id)
|
||||
|
||||
Reference in New Issue
Block a user