Overload resolution for C++ operators (#6092)

Multiple overloads for the same operator are now resolved using overload
resolution.
This change doesn't try to solve all issues with operator lookup.

Moved the operator lookup logic from `import` to `operators` and changed
it to take the args into account.
Use `Sema::LookupOverloadedBinOp()` (with ADL) when looking up operator
functions to create an overload set.

Verified all demos in #6017, #6020 and #6024 still work.

C++ Interop Demo:

```c++
// my_number.h

class MyNumber {
 public:
  explicit MyNumber(int value) : value_(value) {}
  auto value() const -> int { return value_; }

 private:
  int value_;
};

class NotMyNumber {};

auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber;
auto operator+(NotMyNumber lhs, NotMyNumber rhs) -> NotMyNumber;
```

```c++
// my_number.cpp

#include "my_number.h"

auto operator+(MyNumber lhs, MyNumber rhs) -> MyNumber {
  return MyNumber(lhs.value() + rhs.value());
}

auto operator+(NotMyNumber lhs, NotMyNumber /*rhs*/) -> NotMyNumber {
  return lhs;
}
```

```carbon
// main.carbon

library "Main";

import Core library "io";
import Cpp library "my_number.h";

fn Run() -> i32 {
  // Arithmetic
  var num1: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14);
  var num2: Cpp.MyNumber = Cpp.MyNumber.MyNumber(5);
  Core.Print(num1.value());
  Core.Print(num2.value());
  Core.Print((num1 + num2).value());

  return 0;
}
```

**After this change:**

```shell
$ clang -c my_number.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link my_number.o main.o --output=demo
$ ./demo
14
5
19
```

**Before this change**

```shell
$ bazel-bin/toolchain/carbon compile main.carbon
main.carbon:14:15: error: semantics TODO: `Unsupported: Lookup succeeded but couldn't find a single result; LookupResultKind: 3`
  Core.Print((num1 + num2).value());
              ^~~~~~~~~~~
main.carbon:14:15: note: in `Cpp` operator `AddWith` lookup
  Core.Print((num1 + num2).value());
              ^~~~~~~~~~~
```

Part of https://github.com/carbon-language/carbon-lang/issues/5995.
This commit is contained in:
Boaz Brickner
2025-09-19 16:42:46 +00:00
committed by GitHub
parent 05c9fd768e
commit ef488f00fa
10 changed files with 445 additions and 426 deletions
+6 -226
View File
@@ -527,41 +527,6 @@ static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
return lookup;
}
// Looks up the given declaration name in the Clang AST in a specific scope.
// Returns the found declaration and its access. If not found, returns
// `nullopt`. If there's not a single result, returns `nullptr` and default
// access.
static auto ClangLookupDeclarationName(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id,
clang::DeclarationName name)
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
auto lookup = ClangLookup(context, scope_id, name);
if (!lookup) {
return std::nullopt;
}
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
nullptr, clang::AccessSpecifier::AS_none};
// Access checks are performed separately by the Carbon name lookup logic.
lookup->suppressAccessDiagnostics();
if (!lookup->isSingleResult()) {
// Clang will diagnose ambiguous lookup results for us.
if (!lookup->isAmbiguous()) {
context.TODO(loc_id,
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
"find a single result; LookupResultKind: {0}",
static_cast<int>(lookup->getResultKind())));
}
return result;
}
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
return result;
}
// Looks up for constructors in the class scope and returns the lookup result.
static auto ClangConstructorLookup(Context& context,
SemIR::NameScopeId scope_id)
@@ -1894,11 +1859,8 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
// Attempts to import a set of declarations. Returns `false` if an error was
// produced, `true` otherwise.
// TODO: Merge overload set and operators and remove the `is_overload_set`
// param.
static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
ImportWorklist& worklist,
bool is_overload_set = false) -> bool {
ImportWorklist& worklist) -> bool {
// Walk the dependency graph in depth-first order, and import declarations
// once we've imported all of their dependencies.
while (!worklist.empty()) {
@@ -1924,7 +1886,7 @@ static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
// Functions that are part of the overload set are imported at a later
// point, once the overload resolution has selected the suitable function
// for the call.
if (is_overload_set && decl->getAsFunction()) {
if (decl->getAsFunction()) {
continue;
}
auto inst_id = ImportDeclAfterDependencies(context, loc_id, decl);
@@ -2056,10 +2018,9 @@ static auto LookupBuiltinTypes(Context& context, SemIR::LocId loc_id,
return inst_id;
}
// Imports an overloaded function set from Clang to Carbon.
static auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id,
const clang::UnresolvedSet<4>& overload_set)
auto ImportCppOverloadSet(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id,
const clang::UnresolvedSet<4>& overload_set)
-> SemIR::InstId {
SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
SemIR::CppOverloadSet{.name_id = name_id,
@@ -2102,7 +2063,7 @@ static auto ImportOverloadSetAndDependencies(
for (clang::NamedDecl* fn_decl : overloaded_set) {
AddDependentDecl(context, fn_decl, worklist);
}
if (!ImportDeclSet(context, loc_id, worklist, true)) {
if (!ImportDeclSet(context, loc_id, worklist)) {
return SemIR::ErrorInst::InstId;
}
return ImportCppOverloadSet(context, scope_id, name_id, overloaded_set);
@@ -2213,187 +2174,6 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
overload_set);
}
static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
llvm::StringLiteral interface_name,
llvm::StringLiteral op_name)
-> std::optional<clang::OverloadedOperatorKind> {
// Unary operators.
if (interface_name == "Destroy" || interface_name == "As" ||
interface_name == "ImplicitAs") {
// TODO: Support destructors and conversions.
return std::nullopt;
}
// Increment and Decrement.
if (interface_name == "Inc") {
CARBON_CHECK(op_name == "Op");
return clang::OO_PlusPlus;
}
if (interface_name == "Dec") {
CARBON_CHECK(op_name == "Op");
return clang::OO_MinusMinus;
}
// Arithmetic.
if (interface_name == "Negate") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Minus;
}
// Binary operators.
// Arithmetic Operators.
if (interface_name == "AddWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Plus;
}
if (interface_name == "SubWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Minus;
}
if (interface_name == "MulWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Star;
}
if (interface_name == "DivWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Slash;
}
if (interface_name == "ModWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Percent;
}
// Bitwise Operators.
if (interface_name == "BitAndWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Amp;
}
if (interface_name == "BitOrWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Pipe;
}
if (interface_name == "BitXorWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_Caret;
}
if (interface_name == "LeftShiftWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_LessLess;
}
if (interface_name == "RightShiftWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_GreaterGreater;
}
// Compound Assignment Arithmetic Operators.
if (interface_name == "AddAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_PlusEqual;
}
if (interface_name == "SubAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_MinusEqual;
}
if (interface_name == "MulAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_StarEqual;
}
if (interface_name == "DivAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_SlashEqual;
}
if (interface_name == "ModAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_PercentEqual;
}
// Compound Assignment Bitwise Operators.
if (interface_name == "BitAndAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_AmpEqual;
}
if (interface_name == "BitOrAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_PipeEqual;
}
if (interface_name == "BitXorAssignWith") {
CARBON_CHECK(op_name == "Op");
return clang::OO_CaretEqual;
}
// TODO: Add support for `LeftShiftAssignWith` (`OO_LessLessEqual`) and
// `RightShiftAssignWith` (`OO_GreaterGreaterEqual`) when references are
// supported.
// Relational Operators.
if (interface_name == "EqWith") {
if (op_name == "Equal") {
return clang::OO_EqualEqual;
}
CARBON_CHECK(op_name == "NotEqual");
return clang::OO_ExclaimEqual;
}
if (interface_name == "OrderedWith") {
if (op_name == "Less") {
return clang::OO_Less;
}
if (op_name == "Greater") {
return clang::OO_Greater;
}
if (op_name == "LessOrEquivalent") {
return clang::OO_LessEqual;
}
CARBON_CHECK(op_name == "GreaterOrEquivalent");
return clang::OO_GreaterEqual;
}
context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
interface_name));
return std::nullopt;
}
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, Operator op)
-> SemIR::ScopeLookupResult {
Diagnostics::AnnotationScope annotate_diagnostics(
&context.emitter(), [&](auto& builder) {
CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
"in `Cpp` operator `{0}` lookup", std::string);
builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
});
auto op_kind =
GetClangOperatorKind(context, loc_id, op.interface_name, op.op_name);
if (!op_kind) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
// TODO: We should do ADL-only lookup for operators
// (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
// into C++ types. See
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2316950123
auto decl_and_access = ClangLookupDeclarationName(
context, loc_id, scope_id,
context.ast_context().DeclarationNames.getCXXOperatorName(*op_kind));
if (!decl_and_access) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
auto [decl, access] = *decl_and_access;
if (!decl) {
return SemIR::ScopeLookupResult::MakeError();
}
SemIR::InstId inst_id = ImportDeclAndDependencies(context, loc_id, decl);
if (!inst_id.has_value()) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
SemIR::AccessKind access_kind = MapAccess(access);
return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
access_kind);
}
auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
SemIR::ClassId class_id,
SemIR::ClangDeclId clang_decl_id)