C++ interop: Support importing binary operator+ (#5996)

Triggered by calling a binary operator with LHS being an imported C++
class type.

Not supported (yet):
* Multiple overloads.
* Other operators.

C++ Interop Demo:

```c++
// hello_world.h

class C {
 public:
  C(int x) : x_(x) {}
  auto x() const -> int { return x_; }

 private:
  int x_ = 0; 
};

auto operator+ (C c1, C c2) -> C;
```

```c++
// hello_world.cpp

#include "hello_world.h"

#include <cstdio>

auto operator+ (C c1, C c2) -> C {
  printf("Adding %d with %d\n", c1.x(), c2.x());
  return C(c1.x() + c2.x());
}
```

```carbon
// main.carbon

library "Main";

import Cpp library "hello_world.h";

fn Run() -> i32 {
  let c1 : Cpp.C = Cpp.C.C(7);
  let c2 : Cpp.C = Cpp.C.C(8);
  let c3 : Cpp.C = c1 + c2;
  let c4 : Cpp.C = c3 + c2;
  return 0;
}

```

```shell
$ clang -c hello_world.cpp
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo
$ ./demo
Adding 7 with 8
Adding 15 with 8
```

Part of #5995.
This commit is contained in:
Boaz Brickner
2025-09-03 12:01:20 +00:00
committed by GitHub
parent 58de34e534
commit 870c5380a0
8 changed files with 1066 additions and 65 deletions
+148 -59
View File
@@ -37,6 +37,7 @@
#include "toolchain/check/import.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/literal.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/pattern.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/check/type.h"
@@ -485,18 +486,27 @@ auto ImportCppFiles(Context& context,
return std::move(generated_ast);
}
// Looks up the given name in the Clang AST in a specific scope. Returns the
// lookup result if lookup was successful.
static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id)
-> std::optional<clang::LookupResult> {
std::optional<llvm::StringRef> name =
context.names().GetAsStringIfIdentifier(name_id);
if (!name) {
// Special names never exist in C++ code.
return std::nullopt;
// Returns the Clang `DeclContext` for the given name scope. Return the
// translation unit decl if no scope is provided.
static auto GetDeclContext(Context& context, SemIR::NameScopeId scope_id)
-> clang::DeclContext* {
if (!scope_id.has_value()) {
return context.ast_context().getTranslationUnitDecl();
}
auto scope_clang_decl_context_id =
context.name_scopes().Get(scope_id).clang_decl_context_id();
return dyn_cast<clang::DeclContext>(
context.sem_ir().clang_decls().Get(scope_clang_decl_context_id).decl);
}
// 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>> {
clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
CARBON_CHECK(ast);
clang::Sema& sema = ast->getSema();
@@ -505,26 +515,36 @@ static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
// here so that clang's diagnostics can point into the carbon code that uses
// the name.
clang::LookupResult lookup(
sema,
clang::DeclarationNameInfo(
clang::DeclarationName(
sema.getPreprocessor().getIdentifierInfo(*name)),
clang::SourceLocation()),
sema, clang::DeclarationNameInfo(name, clang::SourceLocation()),
clang::Sema::LookupNameKind::LookupOrdinaryName);
auto scope_clang_decl_context_id =
context.name_scopes().Get(scope_id).clang_decl_context_id();
bool found = sema.LookupQualifiedName(
lookup, dyn_cast<clang::DeclContext>(context.sem_ir()
.clang_decls()
.Get(scope_clang_decl_context_id)
.decl));
bool found =
sema.LookupQualifiedName(lookup, GetDeclContext(context, scope_id));
if (!found) {
return std::nullopt;
}
return lookup;
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.
@@ -573,43 +593,48 @@ static auto IsDeclInjectedClassName(const Context& context,
return true;
}
// Returns a Clang DeclarationName for the given `NameId`.
static auto GetDeclarationName(Context& context, SemIR::NameId name_id)
-> std::optional<clang::DeclarationName> {
std::optional<llvm::StringRef> name =
context.names().GetAsStringIfIdentifier(name_id);
if (!name) {
// Special names never exist in C++ code.
return std::nullopt;
}
return clang::DeclarationName(context.sem_ir()
.clang_ast_unit()
->getSema()
.getPreprocessor()
.getIdentifierInfo(*name));
}
// Looks up the given name in the Clang AST in a specific scope, and returns the
// found declaration and its access. If the found declaration is the injected
// class name, looks up constructors instead. If not found, returns `nullopt`.
// If there's not a single result, returns `nullptr` and default access.
// Otherwise, returns the single declaration and its access.
static auto ClangLookup(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
static auto ClangLookupName(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
auto lookup = ClangLookupName(context, scope_id, name_id);
if (!lookup) {
auto declaration_name = GetDeclarationName(context, name_id);
if (!declaration_name) {
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())));
}
auto result =
ClangLookupDeclarationName(context, loc_id, scope_id, *declaration_name);
if (!result) {
return result;
}
if (!IsDeclInjectedClassName(context, scope_id, name_id,
lookup->getFoundDecl())) {
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
clang::NamedDecl* decl = std::get<0>(*result);
if (!decl || !IsDeclInjectedClassName(context, scope_id, name_id, decl)) {
return result;
}
result = {nullptr, clang::AccessSpecifier::AS_none};
clang::DeclContextLookupResult constructors_lookup =
ClangConstructorLookup(context, scope_id);
@@ -1606,6 +1631,29 @@ static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
.call_params_id = call_params_id}};
}
// Returns the Carbon function name for the given function.
static auto GetFunctionName(Context& context, clang::FunctionDecl* clang_decl)
-> SemIR::NameId {
switch (clang_decl->getDeclName().getNameKind()) {
case clang::DeclarationName::CXXConstructorName: {
return context.classes()
.Get(context.insts()
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
context, cast<clang::Decl>(clang_decl->getParent())))
.class_id)
.name_id;
}
case clang::DeclarationName::CXXOperatorName: {
return SemIR::NameId::CppOperator;
}
default: {
return AddIdentifierName(context, clang_decl->getName());
}
}
}
// Creates a `FunctionDecl` and a `Function` without C++ thunk information.
// Returns std::nullopt on failure. The given Clang declaration is assumed to:
// * Have not been imported before.
@@ -1634,19 +1682,8 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
AddPlaceholderInstInNoBlock(context, Parse::NodeId::None, function_decl);
context.imports().push_back(decl_id);
SemIR::NameId function_name_id =
isa<clang::CXXConstructorDecl>(clang_decl)
? context.classes()
.Get(context.insts()
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
context,
cast<clang::Decl>(clang_decl->getParent())))
.class_id)
.name_id
: AddIdentifierName(context, clang_decl->getName());
auto function_info = SemIR::Function{
{.name_id = function_name_id,
{.name_id = GetFunctionName(context, clang_decl),
.parent_scope_id = GetParentNameScopeId(context, clang_decl),
.generic_id = SemIR::GenericId::None,
.first_param_node_id = Parse::NodeId::None,
@@ -1965,7 +2002,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
builder.Note(loc_id, InCppNameLookup, name_id);
});
auto decl_and_access = ClangLookup(context, loc_id, scope_id, name_id);
auto decl_and_access = ClangLookupName(context, loc_id, scope_id, name_id);
if (!decl_and_access) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
@@ -1980,4 +2017,56 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
access);
}
static auto GetOperatorKind(Context& context, SemIR::LocId loc_id,
llvm::StringLiteral interface_name)
-> std::optional<clang::OverloadedOperatorKind> {
if (interface_name == "AddWith") {
return clang::OO_Plus;
}
context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
interface_name));
return std::nullopt;
}
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_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 = GetOperatorKind(context, loc_id, op.interface_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, SemIR::NameScopeId::None,
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);
}
} // namespace Carbon::Check