[Carbon/C++ interop] Add support for C++ overloaded functions (#5891)

As proposed in [Carbon: C++ interop for overloaded functions and
function
templates](https://docs.google.com/document/d/1KUxumZtNe3mY3TsjW2s_ZADOlAaFlrtsLKHVILtqIaM/edit?tab=t.0),
Clang is used to perform the overload resolution using C++ rules, when
an overloaded C++ set is called from Carbon. Once a function is
selected, it's converted into a Carbon function and called using the
Carbon rules including argument conversions.

A single non-templated function is treated the same way as an overload
set and the same rules apply for its call.
Template functions are not supported yet.

Demo:

a) Non-templated function calls:

```c++
// --- overloads.h

auto foo(int a, short b) -> void;
auto foo(double a) -> void;
auto foo(int a) -> void;
```

```c++
// overloads.cpp

#include "overloads.h"
#include <cstdio>

auto foo(int a, short b) -> void {
  printf("hello from foo_int_short(%d, %d) \n", a, b);
}
auto foo(double a) -> void { printf("hello from foo_double(%f) \n", a); }
auto foo(int a) -> void { printf("hello from foo_int(%d) \n", a); }
```
```c++
library "Main";

import Cpp library "overloads.h";

fn Run() -> i32 {
  Cpp.foo(1.1 as f64);
  return 0;
}
```
```
$ clang -c overloads.cpp 
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link overloads.o main.o --output=demo
$ ./demo
hello from foo_double(1.100000) 
```

b) Constructors:
```c++
// --- constructor_overloads.h
class C {
 public:
  C();
  C(int a, int b);
};
```

```c++
// constructor_overloads.cpp
#include "constructor_overloads.h"
#include <cstdio>

C::C() { printf("hello from C() \n"); }
C::C(int a, int b) { printf("hello from C(%d, %d) \n", a, b); }
```
```c++
library "Main";

import Cpp library "constructor_overloads.h";

fn Run() -> i32 {
  let c1: Cpp.C = Cpp.C.C();
  let c2: Cpp.C = Cpp.C.C(1, 2);
  return 0;
}
```

```
$ clang -c constructor_overloads.cpp 
$ bazel-bin/toolchain/carbon compile main.carbon
$ bazel-bin/toolchain/carbon link constructor_overloads.o main.o \--output=demo
$ ./demo
hello from C() 
hello from C(1, 2) 
```


Follow-ups:

- `Cpp.foo({})` - proper handling of struct literals as call args.
- Fix access for overloaded sets.
- Fix tests:
- Method calls: `error: missing object argument in method call
[MissingObjectInMethodCall]` in tests.
    - Fix `toolchain/check/testdata/interop/cpp/import.carbon` test.
    - Fix `enums` support.
    - Fix `str` -> `std::string_view` mapping.


Part of #5915
This commit is contained in:
Ivana Ivanovska
2025-09-15 12:29:49 +00:00
committed by GitHub
parent 20ac6b9270
commit 12ddfb9c7c
61 changed files with 4633 additions and 2252 deletions
+169 -68
View File
@@ -12,6 +12,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/FileManager.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
@@ -49,6 +50,7 @@
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/clang_decl.h"
#include "toolchain/sem_ir/class.h"
#include "toolchain/sem_ir/cpp_overload_set.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/inst.h"
@@ -500,14 +502,9 @@ static auto GetDeclContext(Context& context, SemIR::NameScopeId scope_id)
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>> {
static auto ClangLookup(Context& context, SemIR::NameScopeId scope_id,
clang::DeclarationName name)
-> std::optional<clang::LookupResult> {
clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
CARBON_CHECK(ast);
clang::Sema& sema = ast->getSema();
@@ -526,25 +523,41 @@ static auto ClangLookupDeclarationName(Context& context, SemIR::LocId loc_id,
return std::nullopt;
}
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();
lookup->suppressAccessDiagnostics();
if (!lookup.isSingleResult()) {
if (!lookup->isSingleResult()) {
// Clang will diagnose ambiguous lookup results for us.
if (!lookup.isAmbiguous()) {
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())));
static_cast<int>(lookup->getResultKind())));
}
return result;
}
result = {lookup.getFoundDecl(), lookup.begin().getAccess()};
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
return result;
}
@@ -609,52 +622,18 @@ static auto GetDeclarationName(Context& context, SemIR::NameId name_id)
.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 ClangLookupName(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
// Looks up the given name in the Clang AST in a specific scope. Returns the
// lookup result if lookup was successful.
// TODO: Merge this with `ClangLookupDeclarationName`.
static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
SemIR::NameId name_id)
-> std::optional<clang::LookupResult> {
auto declaration_name = GetDeclarationName(context, name_id);
if (!declaration_name) {
return std::nullopt;
}
auto result =
ClangLookupDeclarationName(context, loc_id, scope_id, *declaration_name);
if (!result) {
return result;
}
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);
llvm::SmallVector<clang::CXXConstructorDecl*> constructors;
for (clang::Decl* decl : constructors_lookup) {
auto* constructor = cast<clang::CXXConstructorDecl>(decl);
if (constructor->isDeleted() || constructor->isCopyOrMoveConstructor()) {
continue;
}
constructors.push_back(constructor);
}
if (constructors.size() != 1) {
context.TODO(
loc_id,
llvm::formatv("Unsupported: Constructors lookup succeeded but couldn't "
"find a single result; Found {0} constructors",
constructors.size()));
return result;
}
result = {constructors[0], constructors[0]->getAccess()};
return result;
return ClangLookup(context, scope_id, *declaration_name);
}
// Returns whether `decl` already mapped to an instruction.
@@ -1650,12 +1629,8 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
return function_decl.function_id;
}
// Imports a function declaration from Clang to Carbon. If successful, returns
// the new Carbon function declaration `InstId`. If the declaration was already
// imported, returns the mapped instruction.
static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
clang::FunctionDecl* clang_decl)
-> SemIR::InstId {
auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
clang::FunctionDecl* clang_decl) -> SemIR::InstId {
// Check if the declaration is already mapped.
if (SemIR::InstId existing_inst_id =
LookupClangDeclInstId(context, clang_decl);
@@ -1844,7 +1819,7 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
clang::Decl* clang_decl)
-> SemIR::InstId {
if (auto* clang_function_decl = clang_decl->getAsFunction()) {
return ImportFunctionDecl(context, loc_id, clang_function_decl);
return ImportCppFunctionDecl(context, loc_id, clang_function_decl);
}
if (auto* clang_namespace_decl = dyn_cast<clang::NamespaceDecl>(clang_decl)) {
return ImportNamespaceDecl(context, clang_namespace_decl);
@@ -1888,8 +1863,11 @@ 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 {
ImportWorklist& worklist,
bool is_overload_set = false) -> bool {
// Walk the dependency graph in depth-first order, and import declarations
// once we've imported all of their dependencies.
while (!worklist.empty()) {
@@ -1912,6 +1890,12 @@ static auto ImportDeclSet(Context& context, SemIR::LocId loc_id,
// Second time visiting this declaration (postorder): its dependencies are
// already imported, so we can import it now.
auto* decl = worklist.pop_back_val().decl;
// 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()) {
continue;
}
auto inst_id = ImportDeclAfterDependencies(context, loc_id, decl);
CARBON_CHECK(inst_id.has_value());
if (inst_id == SemIR::ErrorInst::InstId) {
@@ -1986,6 +1970,82 @@ static auto ImportNameDeclIntoScope(Context& context, SemIR::LocId loc_id,
access_kind);
}
// 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)
-> SemIR::InstId {
SemIR::CppOverloadSetId overload_set_id = context.cpp_overload_sets().Add(
SemIR::CppOverloadSet{.name_id = name_id,
.parent_scope_id = scope_id,
.candidate_functions = overload_set});
auto overload_set_inst_id =
// TODO: Add a location.
AddInstInNoBlock<SemIR::CppOverloadSetValue>(
context, Parse::NodeId::None,
{.type_id = GetCppOverloadSetType(context, overload_set_id,
SemIR::SpecificId::None),
.overload_set_id = overload_set_id});
context.imports().push_back(overload_set_inst_id);
return overload_set_inst_id;
}
// Gets the access for an overloaded function set. Returns std::nullopt
// if the access is not the same for all functions in the overload set.
// TODO: Fix to support functions with different access levels.
static auto GetOverloadSetAccess(Context& context, SemIR::LocId loc_id,
const clang::UnresolvedSet<4>& overload_set)
-> std::optional<SemIR::AccessKind> {
clang::AccessSpecifier access = overload_set.begin().getAccess();
for (auto it = overload_set.begin() + 1; it != overload_set.end(); ++it) {
if (it.getAccess() != access) {
context.TODO(
loc_id,
llvm::formatv("Unsupported: Overloaded set with mixed access").str());
return std::nullopt;
}
}
return MapAccess(access);
}
static auto ImportOverloadSetAndDependencies(
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId scope_id,
SemIR::NameId name_id, const clang::UnresolvedSet<4>& overloaded_set)
-> SemIR::InstId {
ImportWorklist worklist;
for (clang::NamedDecl* fn_decl : overloaded_set) {
AddDependentDecl(context, fn_decl, worklist);
}
if (!ImportDeclSet(context, loc_id, worklist, true)) {
return SemIR::ErrorInst::InstId;
}
return ImportCppOverloadSet(context, scope_id, name_id, overloaded_set);
}
// Imports an overloaded function set from Clang to Carbon and adds the
// name into the `NameScope`.
static auto ImportOverloadSetIntoScope(
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId scope_id,
SemIR::NameId name_id, const clang::UnresolvedSet<4>& overload_set)
-> SemIR::ScopeLookupResult {
std::optional<SemIR::AccessKind> access_kind =
GetOverloadSetAccess(context, loc_id, overload_set);
if (!access_kind.has_value()) {
return SemIR::ScopeLookupResult::MakeError();
}
SemIR::InstId inst_id = ImportOverloadSetAndDependencies(
context, loc_id, scope_id, name_id, overload_set);
AddNameToScope(context, scope_id, name_id, access_kind.value(), inst_id);
return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
access_kind.value());
}
// TODO: Refactor this method.
// TODO: Do we need to import the dependences for all functions in the overload
// set?
auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
-> SemIR::ScopeLookupResult {
@@ -1996,19 +2056,60 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
builder.Note(loc_id, InCppNameLookup, name_id);
});
auto decl_and_access = ClangLookupName(context, loc_id, scope_id, name_id);
if (!decl_and_access) {
auto lookup = ClangLookupName(context, scope_id, name_id);
if (!lookup) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
auto [decl, access] = *decl_and_access;
if (!decl) {
// Access checks are performed separately by the Carbon name lookup logic.
lookup->suppressAccessDiagnostics();
if (lookup->isOverloadedResult() ||
(lookup->isSingleResult() &&
lookup->getFoundDecl()->isFunctionOrFunctionTemplate())) {
clang::UnresolvedSet<4> overload_set;
overload_set.append(lookup->begin(), lookup->end());
return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
overload_set);
}
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())));
}
context.name_scopes().AddRequiredName(scope_id, name_id,
SemIR::ErrorInst::InstId);
return SemIR::ScopeLookupResult::MakeError();
}
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id, decl,
access);
if (!IsDeclInjectedClassName(context, scope_id, name_id,
lookup->getFoundDecl())) {
return ImportNameDeclIntoScope(context, loc_id, scope_id, name_id,
lookup->getFoundDecl(),
lookup->begin().getAccess());
}
clang::DeclContextLookupResult constructors_lookup =
ClangConstructorLookup(context, scope_id);
clang::UnresolvedSet<4> overload_set;
for (clang::Decl* decl : constructors_lookup) {
auto* constructor = cast<clang::CXXConstructorDecl>(decl);
if (constructor->isDeleted() || constructor->isCopyOrMoveConstructor()) {
continue;
}
overload_set.addDecl(constructor, constructor->getAccess());
}
if (overload_set.empty()) {
return SemIR::ScopeLookupResult::MakeNotFound();
}
return ImportOverloadSetIntoScope(context, loc_id, scope_id, name_id,
overload_set);
}
static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,