Skip to content

Commit 870c538

Browse files
authored
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.
1 parent 58de34e commit 870c538

File tree

8 files changed

+1066
-65
lines changed

8 files changed

+1066
-65
lines changed

toolchain/check/import_cpp.cpp

Lines changed: 148 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "toolchain/check/import.h"
3838
#include "toolchain/check/inst.h"
3939
#include "toolchain/check/literal.h"
40+
#include "toolchain/check/operator.h"
4041
#include "toolchain/check/pattern.h"
4142
#include "toolchain/check/pattern_match.h"
4243
#include "toolchain/check/type.h"
@@ -485,18 +486,27 @@ auto ImportCppFiles(Context& context,
485486
return std::move(generated_ast);
486487
}
487488

488-
// Looks up the given name in the Clang AST in a specific scope. Returns the
489-
// lookup result if lookup was successful.
490-
static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
491-
SemIR::NameId name_id)
492-
-> std::optional<clang::LookupResult> {
493-
std::optional<llvm::StringRef> name =
494-
context.names().GetAsStringIfIdentifier(name_id);
495-
if (!name) {
496-
// Special names never exist in C++ code.
497-
return std::nullopt;
489+
// Returns the Clang `DeclContext` for the given name scope. Return the
490+
// translation unit decl if no scope is provided.
491+
static auto GetDeclContext(Context& context, SemIR::NameScopeId scope_id)
492+
-> clang::DeclContext* {
493+
if (!scope_id.has_value()) {
494+
return context.ast_context().getTranslationUnitDecl();
498495
}
496+
auto scope_clang_decl_context_id =
497+
context.name_scopes().Get(scope_id).clang_decl_context_id();
498+
return dyn_cast<clang::DeclContext>(
499+
context.sem_ir().clang_decls().Get(scope_clang_decl_context_id).decl);
500+
}
499501

502+
// Looks up the given declaration name in the Clang AST in a specific scope.
503+
// Returns the found declaration and its access. If not found, returns
504+
// `nullopt`. If there's not a single result, returns `nullptr` and default
505+
// access.
506+
static auto ClangLookupDeclarationName(Context& context, SemIR::LocId loc_id,
507+
SemIR::NameScopeId scope_id,
508+
clang::DeclarationName name)
509+
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
500510
clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
501511
CARBON_CHECK(ast);
502512
clang::Sema& sema = ast->getSema();
@@ -505,26 +515,36 @@ static auto ClangLookupName(Context& context, SemIR::NameScopeId scope_id,
505515
// here so that clang's diagnostics can point into the carbon code that uses
506516
// the name.
507517
clang::LookupResult lookup(
508-
sema,
509-
clang::DeclarationNameInfo(
510-
clang::DeclarationName(
511-
sema.getPreprocessor().getIdentifierInfo(*name)),
512-
clang::SourceLocation()),
518+
sema, clang::DeclarationNameInfo(name, clang::SourceLocation()),
513519
clang::Sema::LookupNameKind::LookupOrdinaryName);
514520

515-
auto scope_clang_decl_context_id =
516-
context.name_scopes().Get(scope_id).clang_decl_context_id();
517-
bool found = sema.LookupQualifiedName(
518-
lookup, dyn_cast<clang::DeclContext>(context.sem_ir()
519-
.clang_decls()
520-
.Get(scope_clang_decl_context_id)
521-
.decl));
521+
bool found =
522+
sema.LookupQualifiedName(lookup, GetDeclContext(context, scope_id));
522523

523524
if (!found) {
524525
return std::nullopt;
525526
}
526527

527-
return lookup;
528+
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
529+
nullptr, clang::AccessSpecifier::AS_none};
530+
531+
// Access checks are performed separately by the Carbon name lookup logic.
532+
lookup.suppressAccessDiagnostics();
533+
534+
if (!lookup.isSingleResult()) {
535+
// Clang will diagnose ambiguous lookup results for us.
536+
if (!lookup.isAmbiguous()) {
537+
context.TODO(loc_id,
538+
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
539+
"find a single result; LookupResultKind: {0}",
540+
static_cast<int>(lookup.getResultKind())));
541+
}
542+
543+
return result;
544+
}
545+
546+
result = {lookup.getFoundDecl(), lookup.begin().getAccess()};
547+
return result;
528548
}
529549

530550
// Looks up for constructors in the class scope and returns the lookup result.
@@ -573,43 +593,48 @@ static auto IsDeclInjectedClassName(const Context& context,
573593
return true;
574594
}
575595

596+
// Returns a Clang DeclarationName for the given `NameId`.
597+
static auto GetDeclarationName(Context& context, SemIR::NameId name_id)
598+
-> std::optional<clang::DeclarationName> {
599+
std::optional<llvm::StringRef> name =
600+
context.names().GetAsStringIfIdentifier(name_id);
601+
if (!name) {
602+
// Special names never exist in C++ code.
603+
return std::nullopt;
604+
}
605+
606+
return clang::DeclarationName(context.sem_ir()
607+
.clang_ast_unit()
608+
->getSema()
609+
.getPreprocessor()
610+
.getIdentifierInfo(*name));
611+
}
612+
576613
// Looks up the given name in the Clang AST in a specific scope, and returns the
577614
// found declaration and its access. If the found declaration is the injected
578615
// class name, looks up constructors instead. If not found, returns `nullopt`.
579616
// If there's not a single result, returns `nullptr` and default access.
580617
// Otherwise, returns the single declaration and its access.
581-
static auto ClangLookup(Context& context, SemIR::LocId loc_id,
582-
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
618+
static auto ClangLookupName(Context& context, SemIR::LocId loc_id,
619+
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
583620
-> std::optional<std::tuple<clang::NamedDecl*, clang::AccessSpecifier>> {
584-
auto lookup = ClangLookupName(context, scope_id, name_id);
585-
if (!lookup) {
621+
auto declaration_name = GetDeclarationName(context, name_id);
622+
if (!declaration_name) {
586623
return std::nullopt;
587624
}
588-
589-
std::tuple<clang::NamedDecl*, clang::AccessSpecifier> result{
590-
nullptr, clang::AccessSpecifier::AS_none};
591-
592-
// Access checks are performed separately by the Carbon name lookup logic.
593-
lookup->suppressAccessDiagnostics();
594-
595-
if (!lookup->isSingleResult()) {
596-
// Clang will diagnose ambiguous lookup results for us.
597-
if (!lookup->isAmbiguous()) {
598-
context.TODO(loc_id,
599-
llvm::formatv("Unsupported: Lookup succeeded but couldn't "
600-
"find a single result; LookupResultKind: {0}",
601-
static_cast<int>(lookup->getResultKind())));
602-
}
603-
625+
auto result =
626+
ClangLookupDeclarationName(context, loc_id, scope_id, *declaration_name);
627+
if (!result) {
604628
return result;
605629
}
606630

607-
if (!IsDeclInjectedClassName(context, scope_id, name_id,
608-
lookup->getFoundDecl())) {
609-
result = {lookup->getFoundDecl(), lookup->begin().getAccess()};
631+
clang::NamedDecl* decl = std::get<0>(*result);
632+
if (!decl || !IsDeclInjectedClassName(context, scope_id, name_id, decl)) {
610633
return result;
611634
}
612635

636+
result = {nullptr, clang::AccessSpecifier::AS_none};
637+
613638
clang::DeclContextLookupResult constructors_lookup =
614639
ClangConstructorLookup(context, scope_id);
615640

@@ -1606,6 +1631,29 @@ static auto CreateFunctionParamsInsts(Context& context, SemIR::LocId loc_id,
16061631
.call_params_id = call_params_id}};
16071632
}
16081633

1634+
// Returns the Carbon function name for the given function.
1635+
static auto GetFunctionName(Context& context, clang::FunctionDecl* clang_decl)
1636+
-> SemIR::NameId {
1637+
switch (clang_decl->getDeclName().getNameKind()) {
1638+
case clang::DeclarationName::CXXConstructorName: {
1639+
return context.classes()
1640+
.Get(context.insts()
1641+
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
1642+
context, cast<clang::Decl>(clang_decl->getParent())))
1643+
.class_id)
1644+
.name_id;
1645+
}
1646+
1647+
case clang::DeclarationName::CXXOperatorName: {
1648+
return SemIR::NameId::CppOperator;
1649+
}
1650+
1651+
default: {
1652+
return AddIdentifierName(context, clang_decl->getName());
1653+
}
1654+
}
1655+
}
1656+
16091657
// Creates a `FunctionDecl` and a `Function` without C++ thunk information.
16101658
// Returns std::nullopt on failure. The given Clang declaration is assumed to:
16111659
// * Have not been imported before.
@@ -1634,19 +1682,8 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
16341682
AddPlaceholderInstInNoBlock(context, Parse::NodeId::None, function_decl);
16351683
context.imports().push_back(decl_id);
16361684

1637-
SemIR::NameId function_name_id =
1638-
isa<clang::CXXConstructorDecl>(clang_decl)
1639-
? context.classes()
1640-
.Get(context.insts()
1641-
.GetAs<SemIR::ClassDecl>(LookupClangDeclInstId(
1642-
context,
1643-
cast<clang::Decl>(clang_decl->getParent())))
1644-
.class_id)
1645-
.name_id
1646-
: AddIdentifierName(context, clang_decl->getName());
1647-
16481685
auto function_info = SemIR::Function{
1649-
{.name_id = function_name_id,
1686+
{.name_id = GetFunctionName(context, clang_decl),
16501687
.parent_scope_id = GetParentNameScopeId(context, clang_decl),
16511688
.generic_id = SemIR::GenericId::None,
16521689
.first_param_node_id = Parse::NodeId::None,
@@ -1965,7 +2002,7 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
19652002
builder.Note(loc_id, InCppNameLookup, name_id);
19662003
});
19672004

1968-
auto decl_and_access = ClangLookup(context, loc_id, scope_id, name_id);
2005+
auto decl_and_access = ClangLookupName(context, loc_id, scope_id, name_id);
19692006
if (!decl_and_access) {
19702007
return SemIR::ScopeLookupResult::MakeNotFound();
19712008
}
@@ -1980,4 +2017,56 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
19802017
access);
19812018
}
19822019

2020+
static auto GetOperatorKind(Context& context, SemIR::LocId loc_id,
2021+
llvm::StringLiteral interface_name)
2022+
-> std::optional<clang::OverloadedOperatorKind> {
2023+
if (interface_name == "AddWith") {
2024+
return clang::OO_Plus;
2025+
}
2026+
2027+
context.TODO(loc_id, llvm::formatv("Unsupported operator interface `{0}`",
2028+
interface_name));
2029+
return std::nullopt;
2030+
}
2031+
2032+
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id, Operator op)
2033+
-> SemIR::ScopeLookupResult {
2034+
Diagnostics::AnnotationScope annotate_diagnostics(
2035+
&context.emitter(), [&](auto& builder) {
2036+
CARBON_DIAGNOSTIC(InCppOperatorLookup, Note,
2037+
"in `Cpp` operator `{0}` lookup", std::string);
2038+
builder.Note(loc_id, InCppOperatorLookup, op.interface_name.str());
2039+
});
2040+
2041+
auto op_kind = GetOperatorKind(context, loc_id, op.interface_name);
2042+
if (!op_kind) {
2043+
return SemIR::ScopeLookupResult::MakeNotFound();
2044+
}
2045+
2046+
// TODO: We should do ADL-only lookup for operators
2047+
// (`Sema::ArgumentDependentLookup`), when we support mapping Carbon types
2048+
// into C++ types. See
2049+
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2316950123
2050+
auto decl_and_access = ClangLookupDeclarationName(
2051+
context, loc_id, SemIR::NameScopeId::None,
2052+
context.ast_context().DeclarationNames.getCXXOperatorName(*op_kind));
2053+
2054+
if (!decl_and_access) {
2055+
return SemIR::ScopeLookupResult::MakeNotFound();
2056+
}
2057+
auto [decl, access] = *decl_and_access;
2058+
if (!decl) {
2059+
return SemIR::ScopeLookupResult::MakeError();
2060+
}
2061+
2062+
SemIR::InstId inst_id = ImportDeclAndDependencies(context, loc_id, decl);
2063+
if (!inst_id.has_value()) {
2064+
return SemIR::ScopeLookupResult::MakeNotFound();
2065+
}
2066+
2067+
SemIR::AccessKind access_kind = MapAccess(access);
2068+
return SemIR::ScopeLookupResult::MakeWrappedLookupResult(inst_id,
2069+
access_kind);
2070+
}
2071+
19832072
} // namespace Carbon::Check

toolchain/check/import_cpp.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "llvm/Support/VirtualFileSystem.h"
1212
#include "toolchain/check/context.h"
1313
#include "toolchain/check/diagnostic_helpers.h"
14+
#include "toolchain/check/operator.h"
1415
#include "toolchain/diagnostics/diagnostic_emitter.h"
1516

1617
namespace Carbon::Check {
@@ -31,6 +32,11 @@ auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
3132
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
3233
-> SemIR::ScopeLookupResult;
3334

35+
// Looks up the given operator in the Clang AST generated when importing C++
36+
// code and returns a lookup result.
37+
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id, Operator op)
38+
-> SemIR::ScopeLookupResult;
39+
3440
// Given a Carbon class declaration that was imported from some kind of C++
3541
// declaration, such as a class or enum, attempt to import a corresponding class
3642
// definition. Returns true if nothing went wrong (whether or not a definition

toolchain/check/operator.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "toolchain/check/call.h"
88
#include "toolchain/check/context.h"
99
#include "toolchain/check/generic.h"
10+
#include "toolchain/check/import_cpp.h"
1011
#include "toolchain/check/member_access.h"
1112
#include "toolchain/check/name_lookup.h"
1213
#include "toolchain/sem_ir/ids.h"
@@ -52,10 +53,41 @@ auto BuildUnaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
5253
return PerformCall(context, loc_id, bound_op_id, {});
5354
}
5455

56+
// Returns whether the type of the instruction is a C++ class.
57+
static auto IsOfCppClassType(Context& context, SemIR::InstId inst_id) -> bool {
58+
auto class_type = context.insts().TryGetAs<SemIR::ClassType>(
59+
context.types().GetInstId(context.insts().Get(inst_id).type_id()));
60+
if (!class_type) {
61+
// Not a class.
62+
return false;
63+
}
64+
65+
return context.name_scopes()
66+
.Get(context.classes().Get(class_type->class_id).scope_id)
67+
.is_cpp_scope();
68+
}
69+
5570
auto BuildBinaryOperator(Context& context, SemIR::LocId loc_id, Operator op,
5671
SemIR::InstId lhs_id, SemIR::InstId rhs_id,
5772
MakeDiagnosticBuilderFn missing_impl_diagnoser)
5873
-> SemIR::InstId {
74+
// For binary operators with a C++ class as at least one of the operands, try
75+
// to import and call the C++ operator.
76+
// TODO: Instead of hooking this here, change impl lookup, so that a generic
77+
// constraint such as `T:! Core.Add` is satisfied by C++ class types that are
78+
// addable. See
79+
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308666348
80+
// and
81+
// https://github.com/carbon-language/carbon-lang/pull/5996/files/5d01fa69511b76f87efbc0387f5e40abcf4c911a#r2308664536
82+
if (IsOfCppClassType(context, lhs_id) || IsOfCppClassType(context, rhs_id)) {
83+
SemIR::ScopeLookupResult cpp_lookup_result =
84+
ImportOperatorFromCpp(context, loc_id, op);
85+
if (cpp_lookup_result.is_found()) {
86+
return PerformCall(context, loc_id, cpp_lookup_result.target_inst_id(),
87+
{lhs_id, rhs_id});
88+
}
89+
}
90+
5991
// Look up the operator function.
6092
auto op_fn = GetOperatorOpFunction(context, loc_id, op);
6193

0 commit comments

Comments
 (0)