Add support for C++ member operators (#6112)

Call `Sema::AddMemberOperatorCandidates()` to properly add candidates.
For C++ member operator calls, use the first arg as self.

C++ Interop Demo:

```c++
// my_number.h

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

 private:
  int value_;
};
```

```c++
// my_number.cpp

#include "my_number.h"

auto MyNumber::operator++() -> MyNumber {
  ++value_;
  return *this;;
}
```

```carbon
// main.carbon

library "Main";

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

fn Run() -> i32 {
  var num: Cpp.MyNumber = Cpp.MyNumber.MyNumber(14);
  Core.Print(num.value());
  ++num;
  Core.Print(num.value());
  return 0;
}
```

```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
15
```

Part of https://github.com/carbon-language/carbon-lang/issues/5995.
This commit is contained in:
Boaz Brickner
2025-09-24 10:11:09 +00:00
committed by GitHub
parent 1e7b7e53ae
commit 88dac35ae8
4 changed files with 186 additions and 87 deletions
+74 -13
View File
@@ -7,10 +7,12 @@
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/Sema.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/cpp/import.h"
#include "toolchain/check/cpp/location.h"
#include "toolchain/check/cpp/type_mapping.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
@@ -56,29 +58,39 @@ static auto AddOverloadCandidataes(clang::Sema& sema,
cast<clang::CXXRecordDecl>(template_decl->getDeclContext()),
ExplicitTemplateArgs, self_type, self_classification, args,
candidate_set, SuppressUserConversions, PartialOverloading);
} else if (method_decl->isOverloadedOperator()) {
sema.AddMemberOperatorCandidates(method_decl->getOverloadedOperator(),
candidate_set.getLocation(), args,
candidate_set);
} else {
sema.AddMethodCandidate(method_decl, found_decl,
method_decl->getParent(), self_type,
self_classification, args, candidate_set,
SuppressUserConversions, PartialOverloading);
}
} else if (template_decl) {
sema.AddTemplateOverloadCandidate(
template_decl, found_decl, ExplicitTemplateArgs, args, candidate_set,
SuppressUserConversions, PartialOverloading);
} else {
if (template_decl) {
sema.AddTemplateOverloadCandidate(
template_decl, found_decl, ExplicitTemplateArgs, args,
candidate_set, SuppressUserConversions, PartialOverloading);
} else {
sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
SuppressUserConversions, PartialOverloading);
}
sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
SuppressUserConversions, PartialOverloading);
}
}
}
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
SemIR::CppOverloadSetId overload_set_id,
SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids)
// Returns whether the decl is an operator member function.
static auto IsOperatorMethodDecl(clang::Decl* decl) -> bool {
auto* clang_method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
return clang_method_decl && clang_method_decl->isOverloadedOperator();
}
// Resolve which function to call, or returns an error instruction if overload
// resolution failed.
static auto ResolveCalleeId(Context& context, SemIR::LocId loc_id,
SemIR::CppOverloadSetId overload_set_id,
SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids)
-> SemIR::InstId {
// Register an annotation scope to flush any Clang diagnostics when we return.
// This is important to ensure that Clang diagnostics are properly interleaved
@@ -125,7 +137,10 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
CARBON_CHECK(best_viable_fn->Function);
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
SemIR::InstId result = ImportCppFunctionDecl(
context, loc_id, best_viable_fn->Function, arg_exprs.size());
context, loc_id, best_viable_fn->Function,
// If this is an operator method, the first arg will be used as self.
arg_exprs.size() -
(IsOperatorMethodDecl(best_viable_fn->Function) ? 1 : 0));
return result;
}
case clang::OverloadingResult::OR_No_Viable_Function: {
@@ -154,4 +169,50 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
}
}
// Returns whether the function is an imported C++ operator member function.
static auto IsCppOperatorMethod(Context& context, SemIR::FunctionId function_id)
-> bool {
SemIR::ClangDeclId clang_decl_id =
context.functions().Get(function_id).clang_decl_id;
return clang_decl_id.has_value() &&
IsOperatorMethodDecl(
context.clang_decls().Get(clang_decl_id).key.decl);
}
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
SemIR::CppOverloadSetId overload_set_id,
SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids)
-> CppOverloadResolutionResult {
CppOverloadResolutionResult result = {
.callee_id =
ResolveCalleeId(context, loc_id, overload_set_id, self_id, arg_ids),
.arg_ids = arg_ids};
SemIR::Callee callee = GetCallee(context.sem_ir(), result.callee_id);
CARBON_KIND_SWITCH(callee) {
case CARBON_KIND(SemIR::CalleeError _): {
result.callee_id = SemIR::ErrorInst::InstId;
return result;
}
case CARBON_KIND(SemIR::CalleeFunction fn): {
CARBON_CHECK(!fn.self_id.has_value());
if (self_id.has_value()) {
// Preserve the `self` argument from the original callee.
fn.self_id = self_id;
} else if (IsCppOperatorMethod(context, fn.function_id)) {
// Adjust `self` and args for C++ overloaded operator methods.
fn.self_id = result.arg_ids.consume_front();
}
result.callee_function = fn;
return result;
}
case CARBON_KIND(SemIR::CalleeCppOverloadSet _): {
CARBON_FATAL("overloads can't be recursive");
}
case CARBON_KIND(SemIR::CalleeNonFunction _): {
CARBON_FATAL("overloads should produce functions");
}
}
}
} // namespace Carbon::Check