mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
Add support for enum comparisons and bitwise operators (#7356)
If C++ overload resolution selects a builtin operator candidate for an enum comparison or bitwise operator, provide support for that operator by generating a corresponding Carbon builtin function. This is structured to be easily extensible to other C++ builtin overload candidates if we so choose, but for now the operators defined in the prelude are doing what we want in most cases. Bitwise operators on enums produce the same enum type as a result. This intentionally deviates from C++, where they produce a promoted integral type. Assisted-by: Gemini via Antigravity
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/cpp/overload_resolution.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/check/custom_witness.h"
|
||||
#include "toolchain/check/function.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/check/pattern.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#include "toolchain/sem_ir/builtin_function_kind.h"
|
||||
#include "toolchain/sem_ir/clang_decl.h"
|
||||
#include "toolchain/sem_ir/cpp_initializer_list.h"
|
||||
#include "toolchain/sem_ir/function.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
@@ -481,10 +483,141 @@ static auto LookupCppConversion(Context& context, SemIR::LocId loc_id,
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
static auto FindClangOperator(Context& context, SemIR::LocId loc_id,
|
||||
clang::OverloadedOperatorKind op_kind,
|
||||
llvm::ArrayRef<clang::Expr*> arg_exprs)
|
||||
-> SemIR::InstId;
|
||||
namespace {
|
||||
// Information about a C++ overloaded operator that we might map into a Carbon
|
||||
// builtin function.
|
||||
struct OverloadedOperatorInfo {
|
||||
enum ReturnType { FirstArgType, Bool };
|
||||
|
||||
// The name for the function used to implement this operator. This is usually
|
||||
// `Op`. This mostly only affects the mangled name, but might show up in
|
||||
// diagnostics.
|
||||
CoreIdentifier op_name = CoreIdentifier::Op;
|
||||
|
||||
// The builtin function used to implement this operator. For now we're only
|
||||
// supporting enum types, so this should be an int builtin.
|
||||
SemIR::BuiltinFunctionKind builtin_kind = SemIR::BuiltinFunctionKind::None;
|
||||
|
||||
// The return type to produce for the overloaded operator.
|
||||
ReturnType return_type;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Determine what kind of Carbon builtin function should be used to represent
|
||||
// the given C++ overloaded operator.
|
||||
static auto GetBuiltinOperatorInfo(clang::OverloadedOperatorKind kind)
|
||||
-> OverloadedOperatorInfo {
|
||||
using OperatorTable =
|
||||
std::array<OverloadedOperatorInfo, clang::NUM_OVERLOADED_OPERATORS>;
|
||||
static constexpr OperatorTable OpTable = [] {
|
||||
OperatorTable table = {};
|
||||
|
||||
// Bitwise operators. In C++, the return type is computed with the usual
|
||||
// arithmetic conversions, but we will just use the type of the arguments.
|
||||
table[clang::OO_Amp] = {
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntAnd,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
|
||||
table[clang::OO_Pipe] = {
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntOr,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
|
||||
table[clang::OO_Caret] = {
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntXor,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
|
||||
table[clang::OO_Tilde] = {
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntComplement,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
|
||||
|
||||
// Comparison operators.
|
||||
table[clang::OO_EqualEqual] = {
|
||||
.op_name = CoreIdentifier::Equal,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntEq,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
table[clang::OO_ExclaimEqual] = {
|
||||
.op_name = CoreIdentifier::NotEqual,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntNeq,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
table[clang::OO_Less] = {
|
||||
.op_name = CoreIdentifier::Less,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntLess,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
table[clang::OO_LessEqual] = {
|
||||
.op_name = CoreIdentifier::LessOrEquivalent,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntLessEq,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
table[clang::OO_Greater] = {
|
||||
.op_name = CoreIdentifier::Greater,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntGreater,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
table[clang::OO_GreaterEqual] = {
|
||||
.op_name = CoreIdentifier::GreaterOrEquivalent,
|
||||
.builtin_kind = SemIR::BuiltinFunctionKind::IntGreaterEq,
|
||||
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
|
||||
|
||||
return table;
|
||||
}();
|
||||
return OpTable[kind];
|
||||
}
|
||||
|
||||
// Builds a Carbon builtin function declaration corresponding to an overload
|
||||
// candidate that selected a C++ builtin operator. Returns None if no
|
||||
// corresponding builtin function could or should be built.
|
||||
static auto TryBuildBuiltinOperator(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
clang::OverloadedOperatorKind op_kind,
|
||||
clang::OverloadCandidateSet::iterator candidate) -> SemIR::InstId {
|
||||
auto info = GetBuiltinOperatorInfo(op_kind);
|
||||
if (info.builtin_kind == SemIR::BuiltinFunctionKind::None) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
// Import the argument types. For now, we only accept enum types.
|
||||
// TODO: Consider expanding this to other types.
|
||||
llvm::SmallVector<SemIR::TypeId, 2> arg_type_ids;
|
||||
for (const auto& conversion : candidate->Conversions) {
|
||||
// Get the type of the argument that overload resolution wanted to pass to
|
||||
// the overload candidate, after any user-defined implicit conversions but
|
||||
// before the final standard conversion sequence, to find an enum type prior
|
||||
// to promotion.
|
||||
clang::QualType converted_type;
|
||||
if (conversion.isStandard()) {
|
||||
converted_type = conversion.Standard.getFromType();
|
||||
} else if (conversion.isUserDefined()) {
|
||||
converted_type = conversion.UserDefined.After.getFromType();
|
||||
} else {
|
||||
// Unexpected kind of conversion sequence.
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
if (!converted_type->isEnumeralType()) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
auto arg_type_id = ImportCppType(context, loc_id, converted_type).type_id;
|
||||
if (!arg_type_id.has_value() || arg_type_id == SemIR::ErrorInst::TypeId) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
arg_type_ids.push_back(arg_type_id);
|
||||
}
|
||||
CARBON_CHECK(arg_type_ids.size() == 1 || arg_type_ids.size() == 2);
|
||||
|
||||
// For now we only accept homogeneous operators.
|
||||
if (arg_type_ids.size() == 2 && arg_type_ids[0] != arg_type_ids[1]) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
// Compute the return type.
|
||||
auto return_type_id = SemIR::TypeId::None;
|
||||
switch (info.return_type) {
|
||||
case OverloadedOperatorInfo::FirstArgType:
|
||||
return_type_id = arg_type_ids[0];
|
||||
break;
|
||||
case OverloadedOperatorInfo::Bool:
|
||||
return_type_id =
|
||||
context.types().GetTypeIdForTypeInstId(SemIR::BoolType::TypeInstId);
|
||||
break;
|
||||
}
|
||||
|
||||
return MakeBuiltinOperatorFunction(context, arg_type_ids, return_type_id,
|
||||
info.op_name, info.builtin_kind);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct DiagnoseIncompleteOperandTypeInCppOperatorLookup {
|
||||
@@ -503,6 +636,11 @@ struct DiagnoseIncompleteOperandTypeInCppOperatorLookup {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static auto FindClangOperator(Context& context, SemIR::LocId loc_id,
|
||||
clang::OverloadedOperatorKind op_kind,
|
||||
llvm::ArrayRef<clang::Expr*> arg_exprs)
|
||||
-> SemIR::InstId;
|
||||
|
||||
auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
llvm::ArrayRef<SemIR::TypeId> arg_type_ids)
|
||||
-> SemIR::InstId {
|
||||
@@ -554,7 +692,6 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
|
||||
if (arg_type_ids.size() == 1) {
|
||||
return FindClangOperator(context, loc_id, *op_kind, {&arg0.expression});
|
||||
}
|
||||
|
||||
CARBON_CHECK(arg_type_ids.size() == 2);
|
||||
cpp_type = MapToCppType(context, arg_type_ids[1]);
|
||||
if (cpp_type.isNull()) {
|
||||
@@ -649,7 +786,10 @@ static auto FindClangOperator(Context& context, SemIR::LocId loc_id,
|
||||
if (!best_viable_fn->Function) {
|
||||
// The best viable candidate was a builtin. Let the Carbon operator
|
||||
// machinery handle that.
|
||||
return SemIR::InstId::None;
|
||||
CARBON_CHECK(!best_viable_fn->RewriteKind,
|
||||
"Rewrite targeted builtin operator");
|
||||
return TryBuildBuiltinOperator(context, loc_id, op_kind,
|
||||
best_viable_fn);
|
||||
}
|
||||
if (best_viable_fn->RewriteKind) {
|
||||
context.TODO(
|
||||
|
||||
Reference in New Issue
Block a user