Skip to content

Commit 15680ba

Browse files
Support calling functions with explicit template arguments. (#6814)
Treat the initial sequence ofarguments in a call to a C++ function up to and including the last argument that is a type or template as being the explicit template arguments for the call, rather than rejecting them because they can't be converted to the parameter types. Implements the current direction on leads issue #6768, except that no syntax for explicitly annotating an argument as being a template argument is provided. --------- Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
1 parent 6dba8ee commit 15680ba

File tree

8 files changed

+315
-65
lines changed

8 files changed

+315
-65
lines changed

toolchain/check/convert.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,10 +2112,12 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
21122112
}
21132113

21142114
auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
2115-
SemIR::InstId expr_id, SemIR::TypeId type_id)
2116-
-> SemIR::InstId {
2115+
SemIR::InstId expr_id, SemIR::TypeId type_id,
2116+
bool diagnose) -> SemIR::InstId {
21172117
return Convert(context, loc_id, expr_id,
2118-
{.kind = ConversionTarget::Value, .type_id = type_id});
2118+
{.kind = ConversionTarget::Value,
2119+
.type_id = type_id,
2120+
.diagnose = diagnose});
21192121
}
21202122

21212123
auto ConvertToValueOrRefOfType(Context& context, SemIR::LocId loc_id,
@@ -2198,8 +2200,8 @@ static auto DiagnoseTypeExprEvaluationFailure(Context& context,
21982200

21992201
auto ExprAsType(Context& context, SemIR::LocId loc_id, SemIR::InstId value_id,
22002202
bool diagnose) -> TypeExpr {
2201-
auto type_as_inst_id =
2202-
ConvertToValueOfType(context, loc_id, value_id, SemIR::TypeType::TypeId);
2203+
auto type_as_inst_id = ConvertToValueOfType(
2204+
context, loc_id, value_id, SemIR::TypeType::TypeId, diagnose);
22032205
if (type_as_inst_id == SemIR::ErrorInst::InstId) {
22042206
return {.inst_id = SemIR::ErrorInst::TypeInstId,
22052207
.type_id = SemIR::ErrorInst::TypeId};

toolchain/check/convert.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ struct ConversionTarget {
7070
// If this is not null or empty, its last element must be storage_id.
7171
PendingBlock* storage_access_block = nullptr;
7272
// Whether failure of conversion is an error and is diagnosed to the user.
73-
// When looking for a possible conversion but with graceful fallback, diagnose
74-
// should be false.
73+
// When looking for a possible conversion but with graceful fallback,
74+
// `diagnose` should be false. If `diagnose` is false, an `ErrorInst` may be
75+
// returned, but it must be discarded.
7576
bool diagnose = true;
7677

7778
// Are we converting this value into an initializer for an object?
@@ -117,9 +118,14 @@ auto ConvertToValueOrRefExpr(Context& context, SemIR::InstId expr_id)
117118
-> SemIR::InstId;
118119

119120
// Converts `expr_id` to a value expression of type `type_id`.
121+
//
122+
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
123+
// looking to see if a conversion is possible but with graceful fallback. If
124+
// `diagnose` is false, an `ErrorInst` may be returned, but it must be
125+
// discarded.
120126
auto ConvertToValueOfType(Context& context, SemIR::LocId loc_id,
121-
SemIR::InstId expr_id, SemIR::TypeId type_id)
122-
-> SemIR::InstId;
127+
SemIR::InstId expr_id, SemIR::TypeId type_id,
128+
bool diagnose = true) -> SemIR::InstId;
123129

124130
// Convert the given expression to a value or reference expression of the given
125131
// type.
@@ -176,7 +182,9 @@ inline constexpr TypeExpr TypeExpr::None = {.inst_id = SemIR::TypeInstId::None,
176182
// Converts an expression for use as a type.
177183
//
178184
// If `diagnose` is true, errors are diagnosed to the user. Set it to false when
179-
// looking to see if a conversion is possible but with graceful fallback.
185+
// looking to see if a conversion is possible but with graceful fallback. If
186+
// `diagnose` is false, an `ErrorInst` may be returned, but it must be
187+
// discarded.
180188
//
181189
// TODO: Most of the callers of this function discard the `inst_id` and lose
182190
// track of the conversion. In most cases we should be retaining that as the

toolchain/check/cpp/call.cpp

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,46 @@
2020

2121
namespace Carbon::Check {
2222

23+
// Returns true if the given instruction can only be a template argument, and
24+
// not a function argument. We classify arguments as definitely being template
25+
// arguments if they are types or the name of a template or generic.
26+
// TODO: We should also have a way to specify that an argument is a non-type
27+
// template argument.
28+
static auto IsTemplateArg(Context& context, SemIR::InstId arg_id) -> bool {
29+
auto arg_type_id = context.insts().Get(arg_id).type_id();
30+
auto arg_type = context.types().GetAsInst(arg_type_id);
31+
return arg_type
32+
.IsOneOf<SemIR::TypeType, SemIR::FacetType, SemIR::CppTemplateNameType,
33+
SemIR::GenericClassType, SemIR::GenericInterfaceType,
34+
SemIR::GenericNamedConstraintType>();
35+
}
36+
37+
// Splits a call argument list into a list of template arguments followed by a
38+
// list of function arguments. We split the argument list as early as possible,
39+
// subject to the constraint that if an argument is a template argument, it goes
40+
// in the template argument list.
41+
static auto SplitCallArgumentList(Context& context,
42+
llvm::ArrayRef<SemIR::InstId> arg_ids)
43+
-> std::pair<llvm::ArrayRef<SemIR::InstId>, llvm::ArrayRef<SemIR::InstId>> {
44+
for (auto [n, arg_id] : llvm::enumerate(llvm::reverse(arg_ids))) {
45+
if (IsTemplateArg(context, arg_id)) {
46+
return {arg_ids.drop_back(n), arg_ids.take_back(n)};
47+
}
48+
}
49+
// No template arguments found.
50+
return {{}, arg_ids};
51+
}
52+
2353
auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
2454
SemIR::CppOverloadSetId overload_set_id,
2555
SemIR::InstId self_id,
2656
llvm::ArrayRef<SemIR::InstId> arg_ids,
2757
bool is_operator_syntax) -> SemIR::InstId {
28-
SemIR::InstId callee_id = PerformCppOverloadResolution(
29-
context, loc_id, overload_set_id, self_id, arg_ids);
58+
auto [template_arg_ids, function_arg_ids] =
59+
SplitCallArgumentList(context, arg_ids);
60+
auto callee_id =
61+
PerformCppOverloadResolution(context, loc_id, overload_set_id,
62+
template_arg_ids, self_id, function_arg_ids);
3063
SemIR::Callee callee = GetCallee(context.sem_ir(), callee_id);
3164
CARBON_KIND_SWITCH(callee) {
3265
case CARBON_KIND(SemIR::CalleeError _): {
@@ -38,8 +71,8 @@ auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
3871
// Preserve the `self` argument from the original callee.
3972
fn.self_id = self_id;
4073
}
41-
return PerformCallToFunction(context, loc_id, callee_id, fn, arg_ids,
42-
is_operator_syntax);
74+
return PerformCallToFunction(context, loc_id, callee_id, fn,
75+
function_arg_ids, is_operator_syntax);
4376
}
4477
case CARBON_KIND(SemIR::CalleeCppOverloadSet _): {
4578
CARBON_FATAL("overloads can't be recursive");
@@ -79,16 +112,18 @@ static auto MakePlaceholderTemplateArg(Context& context, SemIR::InstId arg_id)
79112
static auto ConvertArgToTemplateArg(
80113
Context& context, clang::TemplateDecl* template_decl,
81114
clang::NamedDecl* param_decl, SemIR::InstId arg_id,
82-
clang::SmallVector<clang::TemplateArgument>* template_args)
115+
clang::SmallVector<clang::TemplateArgument>* template_args, bool diagnose)
83116
-> std::optional<clang::TemplateArgumentLoc> {
84117
if (isa<clang::TemplateTypeParmDecl>(param_decl)) {
85-
auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id);
118+
auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id, diagnose);
86119
if (type.type_id == SemIR::ErrorInst::TypeId) {
87120
return std::nullopt;
88121
}
89122
auto clang_type = MapToCppType(context, type.type_id);
90123
if (clang_type.isNull()) {
91-
context.TODO(arg_id, "unsupported type used as template argument");
124+
if (diagnose) {
125+
context.TODO(arg_id, "unsupported type used as template argument");
126+
}
92127
return std::nullopt;
93128
}
94129
return clang::TemplateArgumentLoc(
@@ -127,6 +162,13 @@ static auto ConvertArgToTemplateArg(
127162
// When evaluating the second template argument, the generic type of
128163
// `T` should be substituted with `i32`.
129164
if (param_type->isInstantiationDependentType()) {
165+
// If we don't want to diagnose errors, create a SFINAE context so that
166+
// Clang knows to suppress error messages.
167+
std::optional<clang::Sema::SFINAETrap> sfinae;
168+
if (!diagnose) {
169+
sfinae.emplace(context.clang_sema());
170+
}
171+
130172
clang::Sema::InstantiatingTemplate inst(
131173
context.clang_sema(), clang::SourceLocation(), param_decl, non_type,
132174
*template_args, clang::SourceRange());
@@ -149,7 +191,7 @@ static auto ConvertArgToTemplateArg(
149191
param_type = context.clang_sema().CheckNonTypeTemplateParameterType(
150192
param_type, non_type->getLocation());
151193
}
152-
if (param_type.isNull()) {
194+
if (param_type.isNull() || (sfinae && sfinae->hasErrorOccurred())) {
153195
return std::nullopt;
154196
}
155197
}
@@ -164,6 +206,7 @@ static auto ConvertArgToTemplateArg(
164206
{
165207
.kind = ConversionTarget::Value,
166208
.type_id = type_expr.type_id,
209+
.diagnose = diagnose,
167210
});
168211

169212
if (converted_inst_id == SemIR::ErrorInst::InstId) {
@@ -221,21 +264,21 @@ static auto ConvertArgToTemplateArg(
221264
}
222265

223266
// TODO: Support other types.
224-
context.TODO(arg_id,
225-
"unsupported argument type for non-type template parameter");
267+
if (diagnose) {
268+
context.TODO(arg_id,
269+
"unsupported argument type for non-type template parameter");
270+
}
226271
return std::nullopt;
227272
}
228273

229274
CARBON_FATAL("Unknown declaration kind for template parameter");
230275
}
231276

232-
// Converts a call argument list into a Clang template argument list for a given
233-
// template. Returns true on success, or false if an error was diagnosed.
234-
static auto ConvertArgsToTemplateArgs(Context& context,
235-
clang::TemplateDecl* template_decl,
236-
llvm::ArrayRef<SemIR::InstId> arg_ids,
237-
clang::TemplateArgumentListInfo& arg_list)
238-
-> bool {
277+
auto ConvertArgsToTemplateArgs(Context& context,
278+
clang::TemplateDecl* template_decl,
279+
llvm::ArrayRef<SemIR::InstId> arg_ids,
280+
clang::TemplateArgumentListInfo& arg_list,
281+
bool diagnose) -> bool {
239282
clang::SmallVector<clang::TemplateArgument> template_args;
240283
for (auto* param_decl : template_decl->getTemplateParameters()->asArray()) {
241284
if (arg_ids.empty()) {
@@ -250,8 +293,9 @@ static auto ConvertArgsToTemplateArgs(Context& context,
250293
param_decl->isTemplateParameterPack() ? std::exchange(arg_ids, {})
251294
: arg_ids.consume_front();
252295
for (auto arg_id : args_for_param) {
253-
if (auto arg = ConvertArgToTemplateArg(context, template_decl, param_decl,
254-
arg_id, &template_args)) {
296+
if (auto arg =
297+
ConvertArgToTemplateArg(context, template_decl, param_decl,
298+
arg_id, &template_args, diagnose)) {
255299
arg_list.addArgument(*arg);
256300
template_args.push_back(arg->getArgument());
257301
} else {

toolchain/check/cpp/call.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010

1111
namespace Carbon::Check {
1212

13+
// Converts a call argument list into a Clang template argument list for a given
14+
// template. Returns true on success, or false if an error was diagnosed.
15+
//
16+
// If `diagnose` is false, errors will be suppressed.
17+
auto ConvertArgsToTemplateArgs(Context& context,
18+
clang::TemplateDecl* template_decl,
19+
llvm::ArrayRef<SemIR::InstId> arg_ids,
20+
clang::TemplateArgumentListInfo& arg_list,
21+
bool diagnose = true) -> bool;
22+
1323
// Checks and builds SemIR for a call to a C++ function in the given overload
1424
// set with self `self_id` and arguments `arg_ids`. `is_operator_syntax`
1525
// indicates that this call was generated from an operator rather than from

toolchain/check/cpp/overload_resolution.cpp

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "clang/Sema/Sema.h"
1010
#include "toolchain/base/kind_switch.h"
1111
#include "toolchain/check/cpp/access.h"
12+
#include "toolchain/check/cpp/call.h"
1213
#include "toolchain/check/cpp/import.h"
1314
#include "toolchain/check/cpp/location.h"
1415
#include "toolchain/check/cpp/operators.h"
@@ -34,18 +35,37 @@ static auto GetCppName(Context& context, SemIR::NameId name_id)
3435
}
3536

3637
// Adds the given overload candidates to the candidate set.
37-
static auto AddOverloadCandidates(clang::Sema& sema,
38-
clang::OverloadCandidateSet& candidate_set,
39-
const clang::UnresolvedSet<4>& functions,
40-
clang::Expr* self_arg,
41-
llvm::ArrayRef<clang::Expr*> args) -> void {
38+
static auto AddOverloadCandidates(
39+
Context& context, clang::OverloadCandidateSet& candidate_set,
40+
const clang::UnresolvedSet<4>& functions,
41+
llvm::ArrayRef<SemIR::InstId> template_arg_ids, clang::Expr* self_arg,
42+
llvm::ArrayRef<clang::Expr*> args) -> void {
43+
clang::Sema& sema = context.clang_sema();
44+
4245
constexpr bool SuppressUserConversions = false;
4346
constexpr bool PartialOverloading = false;
44-
constexpr clang::TemplateArgumentListInfo* ExplicitTemplateArgs = nullptr;
4547

4648
for (auto found_decl : functions.pairs()) {
4749
auto* decl = found_decl->getUnderlyingDecl();
50+
51+
// Form an explicit template argument list if needed. Note that this is done
52+
// per-candidate, as the conversions performed on the template arguments
53+
// differ based on the corresponding template parameters.
4854
auto* template_decl = dyn_cast<clang::FunctionTemplateDecl>(decl);
55+
clang::TemplateArgumentListInfo explicit_template_arg_storage;
56+
clang::TemplateArgumentListInfo* explicit_template_args = nullptr;
57+
if (!template_arg_ids.empty()) {
58+
if (!template_decl) {
59+
continue;
60+
}
61+
if (!ConvertArgsToTemplateArgs(context, template_decl, template_arg_ids,
62+
explicit_template_arg_storage,
63+
/*diagnose=*/false)) {
64+
continue;
65+
}
66+
explicit_template_args = &explicit_template_arg_storage;
67+
}
68+
4969
auto* fn_decl = template_decl ? template_decl->getTemplatedDecl()
5070
: cast<clang::FunctionDecl>(decl);
5171
auto* method_decl = dyn_cast<clang::CXXMethodDecl>(fn_decl);
@@ -61,7 +81,7 @@ static auto AddOverloadCandidates(clang::Sema& sema,
6181
sema.AddMethodTemplateCandidate(
6282
template_decl, found_decl,
6383
cast<clang::CXXRecordDecl>(template_decl->getDeclContext()),
64-
ExplicitTemplateArgs, self_type, self_classification, args,
84+
explicit_template_args, self_type, self_classification, args,
6585
candidate_set, SuppressUserConversions, PartialOverloading);
6686
} else if (method_decl->isOverloadedOperator()) {
6787
sema.AddMemberOperatorCandidates(method_decl->getOverloadedOperator(),
@@ -75,8 +95,8 @@ static auto AddOverloadCandidates(clang::Sema& sema,
7595
}
7696
} else if (template_decl) {
7797
sema.AddTemplateOverloadCandidate(
78-
template_decl, found_decl, ExplicitTemplateArgs, args, candidate_set,
79-
SuppressUserConversions, PartialOverloading);
98+
template_decl, found_decl, explicit_template_args, args,
99+
candidate_set, SuppressUserConversions, PartialOverloading);
80100
} else {
81101
sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
82102
SuppressUserConversions, PartialOverloading);
@@ -110,11 +130,11 @@ auto CheckCppOverloadAccess(
110130
.highest_allowed_access = allowed_access_kind});
111131
}
112132

113-
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
114-
SemIR::CppOverloadSetId overload_set_id,
115-
SemIR::InstId self_id,
116-
llvm::ArrayRef<SemIR::InstId> arg_ids)
117-
-> SemIR::InstId {
133+
auto PerformCppOverloadResolution(
134+
Context& context, SemIR::LocId loc_id,
135+
SemIR::CppOverloadSetId overload_set_id,
136+
llvm::ArrayRef<SemIR::InstId> template_arg_ids, SemIR::InstId self_id,
137+
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId {
118138
// Register an annotation scope to flush any Clang diagnostics when we return.
119139
// This is important to ensure that Clang diagnostics are properly interleaved
120140
// with Carbon diagnostics.
@@ -148,12 +168,12 @@ auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
148168
: clang::OverloadCandidateSet::CandidateSetKind::CSK_Normal,
149169
overload_set.operator_rewrite_info);
150170

151-
clang::Sema& sema = context.clang_sema();
152-
153-
AddOverloadCandidates(sema, candidate_set, overload_set.candidate_functions,
171+
AddOverloadCandidates(context, candidate_set,
172+
overload_set.candidate_functions, template_arg_ids,
154173
self_expr, arg_exprs);
155174

156175
// Find best viable function among the candidates.
176+
clang::Sema& sema = context.clang_sema();
157177
clang::OverloadCandidateSet::iterator best_viable_fn;
158178
clang::OverloadingResult overloading_result =
159179
candidate_set.BestViableFunction(sema, loc, best_viable_fn);

toolchain/check/cpp/overload_resolution.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@ auto CheckCppOverloadAccess(
2020
SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
2121
SemIR::NameScopeId parent_scope_id = SemIR::NameScopeId::None) -> void;
2222

23-
// Resolves which function to call using Clang overloading resolution, or
24-
// returns an error instruction if overload resolution failed.
23+
// Resolves which function to call using Clang overload resolution. Returns an
24+
// instruction referring to that function, or an error instruction if overload
25+
// resolution failed.
2526
//
2627
// A set with a single non-templated function goes through the same rules for
27-
// overloading resolution. This is to make sure that calls that have no viable
28+
// overload resolution. This is to make sure that calls that have no viable
2829
// implicit conversion sequence are rejected even when an implicit conversion is
2930
// possible. Keeping the same behavior here for consistency and supporting
3031
// migrations so that the migrated callers from C++ remain valid.
31-
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
32-
SemIR::CppOverloadSetId overload_set_id,
33-
SemIR::InstId self_id,
34-
llvm::ArrayRef<SemIR::InstId> arg_ids)
35-
-> SemIR::InstId;
32+
auto PerformCppOverloadResolution(
33+
Context& context, SemIR::LocId loc_id,
34+
SemIR::CppOverloadSetId overload_set_id,
35+
llvm::ArrayRef<SemIR::InstId> template_arg_ids, SemIR::InstId self_id,
36+
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::InstId;
3637

3738
} // namespace Carbon::Check
3839

0 commit comments

Comments
 (0)