Skip to content

Commit 054dfca

Browse files
zygoloidjonmeow
andauthored
Perform overload resolution immediately in C++ operator lookup. (carbon-language#6416)
Don't attempt to defer overload resolution by creating a `CppOverloadSet`; this was incorrect as we weren't saving the complete clang::OverloadCandidateSet, resulting in template candidates not being found. Moreover, saving the overload candidate set would be expensive, as the representation is surprisingly large, and is unnecessary since we're about to build a call. In passing, improve the diagnostics for overload resolution failure to use Clang's operator overload resolution messages rather than its call overload resolution messages. This fixes calls to templated operator overloads, which is the final piece needed for us to successfully compile an iostream-based "Hello world" program. --------- Co-authored-by: Jon Ross-Perkins <[email protected]>
1 parent 167b45c commit 054dfca

File tree

8 files changed

+339
-133
lines changed

8 files changed

+339
-133
lines changed

examples/interop/cpp/hello_world.carbon

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,54 @@
22
// Exceptions. See /LICENSE for license information.
33
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44

5-
import Cpp inline "#include <cstdio>";
6-
7-
fn Run() {
8-
// TODO: Requires class with virtual bases (`basic_ostream`).
9-
// Cpp.std.cout << "Hello world!\n";
10-
11-
// TODO: Requires variadic function.
12-
// Cpp.printf("Hello world!\n");
13-
14-
// TODO: Requires nullable pointer.
15-
// Cpp.puts("Hello world!\n");
16-
17-
// TODO: Requires nullable void pointer.
18-
// Cpp.write(1, "Hello world!\n", 13);
19-
20-
// TODO: Requires Core.String API.
21-
// let message: str = "Hello world!\n\n";
22-
// for (c: char in message) {
23-
// Cpp.putchar((c as u8) as i32);
24-
// }
5+
import Cpp library "<cstdio>";
6+
import Cpp library "<iostream>";
7+
import Cpp library "<string_view>";
8+
import Cpp library "<unistd.h>";
259

10+
// Demonstrate passing `char`s to a C++ function.
11+
fn HelloPutchar() {
2612
let message: array(char, 13) =
2713
('H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n');
2814
for (c: char in message) {
2915
// TODO: u8 should probably have an implicit cast to i32.
3016
Cpp.putchar((c as u8) as i32);
3117
}
18+
19+
// TODO: Use a loop over a string literal instead. Requires IndexWith support
20+
// for str.
21+
//
22+
// let message: str = "Hello world!\n";
23+
// for (c: char in message) {
24+
// Cpp.putchar((c as u8) as i32);
25+
// }
26+
}
27+
28+
// Demonstrate passing a null-terminated string to a C++ function.
29+
fn HelloStdio() {
30+
// TODO: Requires mapping from Optional(const char*) into C++.
31+
// TODO: There should be a better way to interact with functions that expect a
32+
// null-terminated string.
33+
// Cpp.puts(Cpp.std.data("Hello world!\n\0"));
34+
35+
// TODO: Requires variadic function support.
36+
// Cpp.printf("Hello world!\n\0");
37+
}
38+
39+
// Demonstrate passing a string as a void pointer to a C++ function.
40+
fn HelloWrite() {
41+
// TODO: Requires mapping from Optional(const char*) into a C++ const void*.
42+
// let s: str = "Hello world!\n";
43+
// Cpp.write(1, Cpp.std.data(s), Cpp.std.size(s));
44+
}
45+
46+
fn HelloIostreams() {
47+
Cpp.std.cout << "Hello world!\n";
48+
}
49+
50+
fn Run() {
51+
HelloPutchar();
52+
HelloStdio();
53+
HelloWrite();
54+
HelloIostreams();
3255
}

toolchain/check/cpp/call.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@
1414

1515
namespace Carbon::Check {
1616

17-
// Returns whether the function is an imported C++ operator member function.
18-
static auto IsCppOperatorMethod(Context& context, SemIR::FunctionId function_id)
19-
-> bool {
20-
SemIR::ClangDeclId clang_decl_id =
21-
context.functions().Get(function_id).clang_decl_id;
22-
return clang_decl_id.has_value() &&
23-
IsCppOperatorMethodDecl(
24-
context.clang_decls().Get(clang_decl_id).key.decl);
25-
}
26-
2717
auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
2818
SemIR::CppOverloadSetId overload_set_id,
2919
SemIR::InstId self_id,
@@ -41,9 +31,6 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
4131
if (self_id.has_value()) {
4232
// Preserve the `self` argument from the original callee.
4333
fn.self_id = self_id;
44-
} else if (IsCppOperatorMethod(context, fn.function_id)) {
45-
// Adjust `self` and args for C++ overloaded operator methods.
46-
fn.self_id = arg_ids.consume_front();
4734
}
4835
return PerformCallToFunction(context, loc_id, callee_id, fn, arg_ids);
4936
}

toolchain/check/cpp/operators.cpp

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
#include "clang/Sema/Sema.h"
99
#include "toolchain/check/cpp/import.h"
1010
#include "toolchain/check/cpp/location.h"
11+
#include "toolchain/check/cpp/overload_resolution.h"
1112
#include "toolchain/check/cpp/type_mapping.h"
1213
#include "toolchain/check/inst.h"
1314
#include "toolchain/check/type.h"
1415
#include "toolchain/check/type_completion.h"
1516
#include "toolchain/sem_ir/ids.h"
17+
#include "toolchain/sem_ir/typed_insts.h"
1618

1719
namespace Carbon::Check {
1820

@@ -190,36 +192,105 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
190192
}
191193
}
192194

193-
auto arg_exprs = InventClangArgs(context, arg_ids);
194-
if (!arg_exprs.has_value()) {
195+
auto maybe_arg_exprs = InventClangArgs(context, arg_ids);
196+
if (!maybe_arg_exprs.has_value()) {
195197
return SemIR::ErrorInst::InstId;
196198
}
199+
auto& arg_exprs = *maybe_arg_exprs;
197200

198201
clang::SourceLocation loc = GetCppLocation(context, loc_id);
199202
clang::OverloadCandidateSet::OperatorRewriteInfo operator_rewrite_info(
200203
*op_kind, loc, /*AllowRewritten=*/true);
201-
clang::UnresolvedSet<4> functions;
202204
clang::OverloadCandidateSet candidate_set(
203205
loc, clang::OverloadCandidateSet::CSK_Operator, operator_rewrite_info);
206+
207+
clang::Sema& sema = context.clang_sema();
208+
204209
// This works for both unary and binary operators.
205-
context.clang_sema().LookupOverloadedBinOp(candidate_set, *op_kind, functions,
206-
*arg_exprs);
210+
sema.LookupOverloadedBinOp(candidate_set, *op_kind, clang::UnresolvedSet<0>{},
211+
arg_exprs);
207212

208-
for (auto& it : candidate_set) {
209-
if (!it.Function) {
210-
continue;
213+
clang::OverloadCandidateSet::iterator best_viable_fn;
214+
switch (candidate_set.BestViableFunction(sema, loc, best_viable_fn)) {
215+
case clang::OverloadingResult::OR_Success: {
216+
if (!best_viable_fn->Function) {
217+
// The best viable candidate was a builtin. Let the Carbon operator
218+
// machinery handle that.
219+
return SemIR::InstId::None;
220+
}
221+
if (best_viable_fn->RewriteKind) {
222+
context.TODO(
223+
loc_id,
224+
llvm::formatv("Rewriting operator{0} using {1} is not supported",
225+
clang::getOperatorSpelling(
226+
candidate_set.getRewriteInfo().OriginalOperator),
227+
best_viable_fn->Function->getNameAsString()));
228+
return SemIR::ErrorInst::InstId;
229+
}
230+
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
231+
auto result_id = ImportCppFunctionDecl(
232+
context, loc_id, best_viable_fn->Function,
233+
// If this is an operator method, the first arg will be used as self.
234+
arg_ids.size() -
235+
(isa<clang::CXXMethodDecl>(best_viable_fn->Function) ? 1 : 0));
236+
if (auto fn_decl =
237+
context.insts().TryGetAsWithId<SemIR::FunctionDecl>(result_id)) {
238+
CheckCppOverloadAccess(context, loc_id, best_viable_fn->FoundDecl,
239+
fn_decl->inst_id);
240+
} else {
241+
CARBON_CHECK(result_id == SemIR::ErrorInst::InstId);
242+
}
243+
return result_id;
244+
}
245+
case clang::OverloadingResult::OR_No_Viable_Function: {
246+
// OK, didn't find a viable C++ candidate, but this is not an error, as
247+
// there might be a Carbon candidate.
248+
return SemIR::InstId::None;
249+
}
250+
case clang::OverloadingResult::OR_Ambiguous: {
251+
const char* spelling = clang::getOperatorSpelling(*op_kind);
252+
candidate_set.NoteCandidates(
253+
clang::PartialDiagnosticAt(
254+
loc, sema.PDiag(clang::diag::err_ovl_ambiguous_oper_binary)
255+
<< spelling << arg_exprs[0]->getType()
256+
<< arg_exprs[1]->getType()),
257+
sema, clang::OCD_AmbiguousCandidates, arg_exprs, spelling, loc);
258+
return SemIR::ErrorInst::InstId;
211259
}
212-
functions.addDecl(it.Function, it.FoundDecl.getAccess());
260+
case clang::OverloadingResult::OR_Deleted:
261+
const char* spelling = clang::getOperatorSpelling(*op_kind);
262+
auto* message = best_viable_fn->Function->getDeletedMessage();
263+
// The best viable function might be a different operator if the best
264+
// candidate is a rewritten candidate, so use the operator kind of the
265+
// candidate itself in the diagnostic.
266+
candidate_set.NoteCandidates(
267+
clang::PartialDiagnosticAt(
268+
loc, sema.PDiag(clang::diag::err_ovl_deleted_oper)
269+
<< clang::getOperatorSpelling(
270+
best_viable_fn->Function->getOverloadedOperator())
271+
<< (message != nullptr)
272+
<< (message ? message->getString() : llvm::StringRef())),
273+
sema, clang::OCD_AllCandidates, arg_exprs, spelling, loc);
274+
return SemIR::ErrorInst::InstId;
213275
}
214-
215-
return ImportCppOverloadSet(
216-
context, loc_id, SemIR::NameScopeId::None, SemIR::NameId::CppOperator,
217-
/*naming_class=*/nullptr, std::move(functions), operator_rewrite_info);
218276
}
219277

220278
auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool {
221279
auto* clang_method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
222280
return clang_method_decl && clang_method_decl->isOverloadedOperator();
223281
}
224282

283+
auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool {
284+
auto function_type = context.types().TryGetAs<SemIR::FunctionType>(
285+
context.insts().Get(inst_id).type_id());
286+
if (!function_type) {
287+
return false;
288+
}
289+
SemIR::ClangDeclId clang_decl_id =
290+
context.functions().Get(function_type->function_id).clang_decl_id;
291+
return clang_decl_id.has_value() &&
292+
IsCppOperatorMethodDecl(
293+
context.clang_decls().Get(clang_decl_id).key.decl);
294+
}
295+
225296
} // namespace Carbon::Check

toolchain/check/cpp/operators.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ auto LookupCppOperator(Context& context, SemIR::LocId loc_id, Operator op,
2020
// Returns whether the decl is an operator member function.
2121
auto IsCppOperatorMethodDecl(clang::Decl* decl) -> bool;
2222

23+
// Returns whether the specified instruction refers to a C++ overloaded operator
24+
// that is a method. If so, the first operand will be passed as `self` rather
25+
// than as the first argument.
26+
auto IsCppOperatorMethod(Context& context, SemIR::InstId inst_id) -> bool;
27+
2328
} // namespace Carbon::Check
2429

2530
#endif // CARBON_TOOLCHAIN_CHECK_CPP_OPERATORS_H_

toolchain/check/cpp/overload_resolution.cpp

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,27 @@ static auto AddOverloadCandidates(clang::Sema& sema,
8484
}
8585
}
8686

87-
// Checks whether a selected overload is accessible and diagnoses if not.
88-
static auto CheckOverloadAccess(Context& context, SemIR::LocId loc_id,
89-
const SemIR::CppOverloadSet& overload_set,
90-
clang::DeclAccessPair overload,
91-
SemIR::InstId overload_inst_id) -> void {
87+
auto CheckCppOverloadAccess(
88+
Context& context, SemIR::LocId loc_id, clang::DeclAccessPair overload,
89+
SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
90+
SemIR::NameScopeId parent_scope_id) -> void {
9291
SemIR::AccessKind member_access_kind = MapCppAccess(overload);
9392
if (member_access_kind == SemIR::AccessKind::Public) {
9493
return;
9594
}
9695

96+
auto function_id = context.insts().Get(overload_inst_id).function_id;
97+
auto& function = context.functions().Get(function_id);
98+
if (!parent_scope_id.has_value()) {
99+
parent_scope_id = function.parent_scope_id;
100+
}
101+
97102
auto name_scope_const_id = context.constant_values().Get(
98-
context.name_scopes().Get(overload_set.parent_scope_id).inst_id());
103+
context.name_scopes().Get(parent_scope_id).inst_id());
99104
SemIR::AccessKind allowed_access_kind =
100105
GetHighestAllowedAccess(context, loc_id, name_scope_const_id);
101-
CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id),
102-
overload_set.name_id, member_access_kind,
106+
CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id), function.name_id,
107+
member_access_kind,
103108
/*is_parent_access=*/false,
104109
{.constant_id = name_scope_const_id,
105110
.highest_allowed_access = allowed_access_kind});
@@ -155,25 +160,18 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
155160

156161
switch (overloading_result) {
157162
case clang::OverloadingResult::OR_Success: {
158-
// TODO: Handle the cases when Function is null.
159163
CARBON_CHECK(best_viable_fn->Function);
160-
if (best_viable_fn->RewriteKind) {
161-
context.TODO(
162-
loc_id,
163-
llvm::formatv("Rewriting operator{0} using {1} is not supported",
164-
clang::getOperatorSpelling(
165-
candidate_set.getRewriteInfo().OriginalOperator),
166-
best_viable_fn->Function->getNameAsString()));
167-
return SemIR::ErrorInst::InstId;
168-
}
164+
CARBON_CHECK(!best_viable_fn->RewriteKind);
169165
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
170166
SemIR::InstId result_id = ImportCppFunctionDecl(
171-
context, loc_id, best_viable_fn->Function,
172-
// If this is an operator method, the first arg will be used as self.
173-
arg_exprs.size() -
174-
(IsCppOperatorMethodDecl(best_viable_fn->Function) ? 1 : 0));
175-
CheckOverloadAccess(context, loc_id, overload_set,
176-
best_viable_fn->FoundDecl, result_id);
167+
context, loc_id, best_viable_fn->Function, arg_exprs.size());
168+
if (auto fn_decl =
169+
context.insts().TryGetAsWithId<SemIR::FunctionDecl>(result_id)) {
170+
CheckCppOverloadAccess(context, loc_id, best_viable_fn->FoundDecl,
171+
fn_decl->inst_id, overload_set.parent_scope_id);
172+
} else {
173+
CARBON_CHECK(result_id == SemIR::ErrorInst::InstId);
174+
}
177175
return result_id;
178176
}
179177
case clang::OverloadingResult::OR_No_Viable_Function: {

toolchain/check/cpp/overload_resolution.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111

1212
namespace Carbon::Check {
1313

14+
// Checks whether a selected overload is accessible and diagnoses if not.
15+
// `parent_scope_id`, if specified, describes the scope that was named to find
16+
// the overload. If unspecified, we assume the overload was found in the class
17+
// that it is a direct member of, rather than a derived class.
18+
auto CheckCppOverloadAccess(
19+
Context& context, SemIR::LocId loc_id, clang::DeclAccessPair overload,
20+
SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
21+
SemIR::NameScopeId parent_scope_id = SemIR::NameScopeId::None) -> void;
22+
1423
// Resolves which function to call using Clang overloading resolution, or
1524
// returns an error instruction if overload resolution failed.
1625
//

0 commit comments

Comments
 (0)