mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 21:21:04 +01:00
[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:
+169
-68
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user