Files
carbon-lang/toolchain/check/cpp/overload_resolution.cpp
T
Richard SmithandJon Ross-Perkins 054dfca685 Perform overload resolution immediately in C++ operator lookup. (#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 <jperkins@google.com>
2025-11-24 23:01:28 +00:00

204 lines
8.6 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/cpp/overload_resolution.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/Sema.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/cpp/access.h"
#include "toolchain/check/cpp/import.h"
#include "toolchain/check/cpp/location.h"
#include "toolchain/check/cpp/operators.h"
#include "toolchain/check/cpp/type_mapping.h"
#include "toolchain/check/member_access.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/name_scope.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
// Map a Carbon name into a C++ name.
static auto GetCppName(Context& context, SemIR::NameId name_id)
-> clang::DeclarationName {
// TODO: Some special names should probably use different formatting. In
// particular, NameId::CppOperator should probably map back to a
// CXXOperatorName.
auto name_str = context.names().GetFormatted(name_id);
return clang::DeclarationName(&context.ast_context().Idents.get(name_str));
}
// Adds the given overload candidates to the candidate set.
static auto AddOverloadCandidates(clang::Sema& sema,
clang::OverloadCandidateSet& candidate_set,
const clang::UnresolvedSet<4>& functions,
clang::Expr* self_arg,
llvm::ArrayRef<clang::Expr*> args) -> void {
constexpr bool SuppressUserConversions = false;
constexpr bool PartialOverloading = false;
constexpr clang::TemplateArgumentListInfo* ExplicitTemplateArgs = nullptr;
for (auto found_decl : functions.pairs()) {
auto* decl = found_decl->getUnderlyingDecl();
auto* template_decl = dyn_cast<clang::FunctionTemplateDecl>(decl);
auto* fn_decl = template_decl ? template_decl->getTemplatedDecl()
: cast<clang::FunctionDecl>(decl);
auto* method_decl = dyn_cast<clang::CXXMethodDecl>(fn_decl);
if (method_decl && !method_decl->isStatic() &&
!isa<clang::CXXConstructorDecl>(fn_decl)) {
clang::QualType self_type;
clang::Expr::Classification self_classification;
if (self_arg) {
self_type = self_arg->getType();
self_classification = self_arg->Classify(sema.Context);
}
if (template_decl) {
sema.AddMethodTemplateCandidate(
template_decl, found_decl,
cast<clang::CXXRecordDecl>(template_decl->getDeclContext()),
ExplicitTemplateArgs, self_type, self_classification, args,
candidate_set, SuppressUserConversions, PartialOverloading);
} else if (method_decl->isOverloadedOperator()) {
sema.AddMemberOperatorCandidates(method_decl->getOverloadedOperator(),
candidate_set.getLocation(), args,
candidate_set);
} else {
sema.AddMethodCandidate(method_decl, found_decl,
method_decl->getParent(), self_type,
self_classification, args, candidate_set,
SuppressUserConversions, PartialOverloading);
}
} else if (template_decl) {
sema.AddTemplateOverloadCandidate(
template_decl, found_decl, ExplicitTemplateArgs, args, candidate_set,
SuppressUserConversions, PartialOverloading);
} else {
sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
SuppressUserConversions, PartialOverloading);
}
}
}
auto CheckCppOverloadAccess(
Context& context, SemIR::LocId loc_id, clang::DeclAccessPair overload,
SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
SemIR::NameScopeId parent_scope_id) -> void {
SemIR::AccessKind member_access_kind = MapCppAccess(overload);
if (member_access_kind == SemIR::AccessKind::Public) {
return;
}
auto function_id = context.insts().Get(overload_inst_id).function_id;
auto& function = context.functions().Get(function_id);
if (!parent_scope_id.has_value()) {
parent_scope_id = function.parent_scope_id;
}
auto name_scope_const_id = context.constant_values().Get(
context.name_scopes().Get(parent_scope_id).inst_id());
SemIR::AccessKind allowed_access_kind =
GetHighestAllowedAccess(context, loc_id, name_scope_const_id);
CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id), function.name_id,
member_access_kind,
/*is_parent_access=*/false,
{.constant_id = name_scope_const_id,
.highest_allowed_access = allowed_access_kind});
}
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
SemIR::CppOverloadSetId overload_set_id,
SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids)
-> SemIR::InstId {
// Register an annotation scope to flush any Clang diagnostics when we return.
// This is important to ensure that Clang diagnostics are properly interleaved
// with Carbon diagnostics.
Diagnostics::AnnotationScope annotate_diagnostics(&context.emitter(),
[](auto& /*builder*/) {});
// Map Carbon call argument types to C++ types.
clang::Expr* self_expr = nullptr;
if (self_id.has_value()) {
self_expr = InventClangArg(context, self_id);
if (!self_expr) {
return SemIR::ErrorInst::InstId;
}
}
auto maybe_arg_exprs = InventClangArgs(context, arg_ids);
if (!maybe_arg_exprs.has_value()) {
return SemIR::ErrorInst::InstId;
}
auto& arg_exprs = *maybe_arg_exprs;
const SemIR::CppOverloadSet& overload_set =
context.cpp_overload_sets().Get(overload_set_id);
clang::SourceLocation loc = GetCppLocation(context, loc_id);
// Add candidate functions from the name lookup.
clang::OverloadCandidateSet candidate_set(
loc,
overload_set.operator_rewrite_info.OriginalOperator
? clang::OverloadCandidateSet::CandidateSetKind::CSK_Operator
: clang::OverloadCandidateSet::CandidateSetKind::CSK_Normal,
overload_set.operator_rewrite_info);
clang::Sema& sema = context.clang_sema();
AddOverloadCandidates(sema, candidate_set, overload_set.candidate_functions,
self_expr, arg_exprs);
// Find best viable function among the candidates.
clang::OverloadCandidateSet::iterator best_viable_fn;
clang::OverloadingResult overloading_result =
candidate_set.BestViableFunction(sema, loc, best_viable_fn);
switch (overloading_result) {
case clang::OverloadingResult::OR_Success: {
CARBON_CHECK(best_viable_fn->Function);
CARBON_CHECK(!best_viable_fn->RewriteKind);
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
SemIR::InstId result_id = ImportCppFunctionDecl(
context, loc_id, best_viable_fn->Function, arg_exprs.size());
if (auto fn_decl =
context.insts().TryGetAsWithId<SemIR::FunctionDecl>(result_id)) {
CheckCppOverloadAccess(context, loc_id, best_viable_fn->FoundDecl,
fn_decl->inst_id, overload_set.parent_scope_id);
} else {
CARBON_CHECK(result_id == SemIR::ErrorInst::InstId);
}
return result_id;
}
case clang::OverloadingResult::OR_No_Viable_Function: {
candidate_set.NoteCandidates(
clang::PartialDiagnosticAt(
loc, sema.PDiag(clang::diag::err_ovl_no_viable_function_in_call)
<< GetCppName(context, overload_set.name_id)),
sema, clang::OCD_AllCandidates, arg_exprs);
return SemIR::ErrorInst::InstId;
}
case clang::OverloadingResult::OR_Ambiguous: {
candidate_set.NoteCandidates(
clang::PartialDiagnosticAt(
loc, sema.PDiag(clang::diag::err_ovl_ambiguous_call)
<< GetCppName(context, overload_set.name_id)),
sema, clang::OCD_AmbiguousCandidates, arg_exprs);
return SemIR::ErrorInst::InstId;
}
case clang::OverloadingResult::OR_Deleted: {
sema.DiagnoseUseOfDeletedFunction(
loc, clang::SourceRange(loc, loc),
GetCppName(context, overload_set.name_id), candidate_set,
best_viable_fn->Function, arg_exprs);
return SemIR::ErrorInst::InstId;
}
}
}
} // namespace Carbon::Check