mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:20:11 +01:00
The type `type` is now a `FacetType` inst with no constraints. This brings the model implemented in the toolchain into better alignment with the language design. The `SemIR::TypeType` struct remains as a scope for holding the `TypeInstId`, `ConstantId`, and `TypeId` constants, but is not an `InstKind` anymore. The `TypeType` inst looks a lot like singletons, but there are many `FacetType` insts so it doesn't quite fit that model. So we put it alongside singletons with a fixed inst id but refer to it as a more general "builtin" inst that is not a singleton. `Namespace::PackageInstId` is similar, and we group it with `TypeType` conceptually as another builtin instruction with a fixed id. No conversion is needed anymore to use a `type` as a facet, since types also have a `FacetType` type. This simplifies and removes a number of helpers and branches throughout the code. The `TypeType` inst is now part of the constant store, so we end up printing it in the constants block in every test. But it's also named `type` rather than `%type` to preserve the majority of existing formatting behaviour, though this does look different from other constants. Assisted-by: Opus 5 was used to generate a first draft and validate the refactoring. Though nearly everything non-trivial the tool wrote has been modified or rewritten.
362 lines
15 KiB
C++
362 lines
15 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/call.h"
|
|
|
|
#include "clang/Sema/Sema.h"
|
|
#include "clang/Sema/Template.h"
|
|
#include "toolchain/base/kind_switch.h"
|
|
#include "toolchain/check/action.h"
|
|
#include "toolchain/check/call.h"
|
|
#include "toolchain/check/cpp/constant.h"
|
|
#include "toolchain/check/cpp/import.h"
|
|
#include "toolchain/check/cpp/location.h"
|
|
#include "toolchain/check/cpp/operators.h"
|
|
#include "toolchain/check/cpp/overload_resolution.h"
|
|
#include "toolchain/check/cpp/type_mapping.h"
|
|
#include "toolchain/check/literal.h"
|
|
#include "toolchain/sem_ir/function.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/sem_ir/typed_insts.h"
|
|
|
|
namespace Carbon::Check {
|
|
|
|
// Returns true if the given instruction can only be a template argument, and
|
|
// not a function argument. We classify arguments as definitely being template
|
|
// arguments if they are types or the name of a template or generic.
|
|
// TODO: We should also have a way to specify that an argument is a non-type
|
|
// template argument.
|
|
static auto IsTemplateArg(Context& context, SemIR::InstId arg_id) -> bool {
|
|
auto arg_type_id = context.insts().Get(arg_id).type_id();
|
|
auto arg_type = context.types().GetAsInst(arg_type_id);
|
|
return arg_type.IsOneOf<SemIR::FacetType, SemIR::CppTemplateNameType,
|
|
SemIR::GenericClassType, SemIR::GenericInterfaceType,
|
|
SemIR::GenericNamedConstraintType>();
|
|
}
|
|
|
|
// Splits a call argument list into a list of template arguments followed by a
|
|
// list of function arguments. We split the argument list as early as possible,
|
|
// subject to the constraint that if an argument is a template argument, it goes
|
|
// in the template argument list.
|
|
static auto SplitCallArgumentList(Context& context,
|
|
llvm::ArrayRef<SemIR::InstId> arg_ids)
|
|
-> std::pair<llvm::ArrayRef<SemIR::InstId>, llvm::ArrayRef<SemIR::InstId>> {
|
|
for (auto [n, arg_id] : llvm::enumerate(llvm::reverse(arg_ids))) {
|
|
if (IsTemplateArg(context, arg_id)) {
|
|
return {arg_ids.drop_back(n), arg_ids.take_back(n)};
|
|
}
|
|
}
|
|
// No template arguments found.
|
|
return {{}, arg_ids};
|
|
}
|
|
|
|
auto PerformCallToCppFunction(Context& context, SemIR::LocId loc_id,
|
|
SemIR::CppOverloadSetId overload_set_id,
|
|
SemIR::InstId self_id,
|
|
llvm::ArrayRef<SemIR::InstId> arg_ids,
|
|
bool is_desugared) -> SemIR::InstId {
|
|
auto [template_arg_ids, function_arg_ids] =
|
|
SplitCallArgumentList(context, arg_ids);
|
|
auto callee_id = PerformCppOverloadResolution(
|
|
context, loc_id, context.cpp_overload_sets().Get(overload_set_id),
|
|
template_arg_ids, self_id, function_arg_ids);
|
|
SemIR::Callee callee = GetCallee(context.sem_ir(), callee_id);
|
|
CARBON_KIND_SWITCH(callee) {
|
|
case CARBON_KIND(SemIR::CalleeError _): {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
case CARBON_KIND(SemIR::CalleeFunction fn): {
|
|
CARBON_CHECK(!fn.self_id.has_value());
|
|
if (self_id.has_value()) {
|
|
// Preserve the `self` argument from the original callee.
|
|
fn.self_id = self_id;
|
|
}
|
|
return PerformCallToFunction(context, loc_id, callee_id, fn,
|
|
function_arg_ids, is_desugared);
|
|
}
|
|
case CARBON_KIND(SemIR::CalleeCppOverloadSet _): {
|
|
CARBON_FATAL("overloads can't be recursive");
|
|
}
|
|
case CARBON_KIND(SemIR::CalleeNonFunction _): {
|
|
CARBON_FATAL("overloads should produce functions");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synthesize a placeholder `void{}` template argument, that will never be a
|
|
// valid argument for any template parameter. This is used in order to get Clang
|
|
// to diagnose invalid template argument errors for us. The location of the
|
|
// Carbon expression is used as the location of the C++ expression, so
|
|
// Clang's diagnostics will point into the Carbon code.
|
|
//
|
|
// TODO: If Clang ever tries to print the type of the expression or to
|
|
// pretty-print the expression itself, it would print the wrong thing. Currently
|
|
// this doesn't appear to happen, but in principle it could. Ideally we'd add an
|
|
// extension point to Clang to represent a "foreign expression" and use it here
|
|
// instead of creating a bogus placeholder expression.
|
|
static auto MakePlaceholderTemplateArg(Context& context, SemIR::InstId arg_id)
|
|
-> clang::TemplateArgumentLoc {
|
|
auto arg_loc = GetCppLocation(context, SemIR::LocId(arg_id));
|
|
auto void_type = context.ast_context().VoidTy;
|
|
auto* arg = new (context.ast_context()) clang::CXXScalarValueInitExpr(
|
|
void_type,
|
|
context.ast_context().getTrivialTypeSourceInfo(void_type, arg_loc),
|
|
arg_loc);
|
|
return clang::TemplateArgumentLoc(
|
|
clang::TemplateArgument(arg, /*IsCanonical=*/false), arg);
|
|
}
|
|
|
|
// Converts an argument in a call to a C++ template name into a corresponding
|
|
// clang template argument, given the template parameter it will be matched
|
|
// against.
|
|
static auto ConvertArgToTemplateArg(
|
|
Context& context, clang::TemplateDecl* template_decl,
|
|
clang::NamedDecl* param_decl, SemIR::InstId arg_id,
|
|
clang::SmallVector<clang::TemplateArgument>* template_args,
|
|
unsigned argument_pack_index, bool diagnose)
|
|
-> std::optional<clang::TemplateArgumentLoc> {
|
|
if (isa<clang::TemplateTypeParmDecl>(param_decl)) {
|
|
auto type = ExprAsType(context, SemIR::LocId(arg_id), arg_id, diagnose);
|
|
if (type.type_id == SemIR::ErrorInst::TypeId) {
|
|
return std::nullopt;
|
|
}
|
|
auto clang_type = MapToCppType(context, type.type_id);
|
|
if (clang_type.isNull()) {
|
|
if (diagnose) {
|
|
context.TODO(arg_id, "unsupported type used as template argument");
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
return clang::TemplateArgumentLoc(
|
|
clang_type,
|
|
context.ast_context().getTrivialTypeSourceInfo(
|
|
clang_type, GetCppLocation(context, SemIR::LocId(arg_id))));
|
|
}
|
|
|
|
if (isa<clang::TemplateTemplateParmDecl>(param_decl)) {
|
|
auto inst = context.sem_ir().insts().Get(arg_id);
|
|
if (auto template_name_type =
|
|
context.types().TryGetAs<SemIR::CppTemplateNameType>(
|
|
inst.type_id())) {
|
|
clang::TemplateName name(cast<clang::TemplateDecl>(
|
|
context.clang_decls().Get(template_name_type->decl_id).decl()));
|
|
return clang::TemplateArgumentLoc(
|
|
context.ast_context(), clang::TemplateArgument(name),
|
|
/*TemplateKWLoc=*/clang::SourceLocation(),
|
|
clang::NestedNameSpecifierLoc(),
|
|
GetCppLocation(context, SemIR::LocId(arg_id)));
|
|
}
|
|
|
|
// TODO: Eventually we should also support passing Carbon generics as
|
|
// template template arguments.
|
|
return MakePlaceholderTemplateArg(context, arg_id);
|
|
}
|
|
|
|
if (auto* non_type = dyn_cast<clang::NonTypeTemplateParmDecl>(param_decl)) {
|
|
auto param_type = non_type->getType();
|
|
if (non_type->isParameterPack() && non_type->isExpandedParameterPack()) {
|
|
param_type = non_type->getExpansionType(argument_pack_index);
|
|
}
|
|
|
|
// Handle non-type parameters with a dependent type. For example:
|
|
//
|
|
// C++: template<typename T, T N> struct S{};
|
|
// Carbon: Cpp.S(i32, 42)
|
|
//
|
|
// When evaluating the second template argument, the generic type of
|
|
// `T` should be substituted with `i32`.
|
|
if (param_type->isInstantiationDependentType()) {
|
|
// If we don't want to diagnose errors, create a SFINAE context so that
|
|
// Clang knows to suppress error messages.
|
|
std::optional<clang::Sema::SFINAETrap> sfinae;
|
|
if (!diagnose) {
|
|
sfinae.emplace(context.clang_sema());
|
|
}
|
|
|
|
clang::Sema::InstantiatingTemplate inst(
|
|
context.clang_sema(), clang::SourceLocation(), param_decl, non_type,
|
|
*template_args, clang::SourceRange());
|
|
if (inst.isInvalid()) {
|
|
return std::nullopt;
|
|
}
|
|
clang::MultiLevelTemplateArgumentList mltal(template_decl, *template_args,
|
|
/*Final=*/true);
|
|
|
|
mltal.addOuterRetainedLevels(non_type->getDepth());
|
|
if (const auto* pet = param_type->getAs<clang::PackExpansionType>()) {
|
|
clang::Sema::ArgPackSubstIndexRAII subst_index(context.clang_sema(),
|
|
argument_pack_index);
|
|
param_type = context.clang_sema().SubstType(pet->getPattern(), mltal,
|
|
non_type->getLocation(),
|
|
non_type->getDeclName());
|
|
} else {
|
|
param_type = context.clang_sema().SubstType(param_type, mltal,
|
|
non_type->getLocation(),
|
|
non_type->getDeclName());
|
|
}
|
|
|
|
if (!param_type.isNull()) {
|
|
param_type = context.clang_sema().CheckNonTypeTemplateParameterType(
|
|
param_type, non_type->getLocation());
|
|
}
|
|
if (param_type.isNull() || (sfinae && sfinae->hasErrorOccurred())) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// Get the Carbon type corresponding to the parameter's Clang type.
|
|
const auto type_expr =
|
|
ImportCppType(context, SemIR::LocId(arg_id), param_type);
|
|
|
|
// Try to convert the argument to the parameter type.
|
|
const auto converted_inst_id =
|
|
Convert(context, SemIR::LocId(arg_id), arg_id,
|
|
{
|
|
.kind = ConversionTarget::Value,
|
|
.type_id = type_expr.type_id,
|
|
.diagnose = diagnose,
|
|
});
|
|
|
|
if (converted_inst_id == SemIR::ErrorInst::InstId) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// TODO: provide a better location.
|
|
auto template_loc = clang::TemplateArgumentLocInfo(context.ast_context(),
|
|
clang::SourceLocation());
|
|
|
|
auto const_inst_id =
|
|
context.constant_values().GetConstantInstId(converted_inst_id);
|
|
if (const_inst_id.has_value()) {
|
|
if (param_type->isPointerType()) {
|
|
if (auto addr_of =
|
|
context.insts().TryGetAs<SemIR::AddrOf>(const_inst_id)) {
|
|
if (auto* var_decl = GetAsClangVarDecl(context, addr_of->lvalue_id)) {
|
|
clang::TemplateArgument template_arg(var_decl, param_type);
|
|
return clang::TemplateArgumentLoc(template_arg, template_loc);
|
|
}
|
|
|
|
// TODO: support pointers to variables declared in Carbon.
|
|
}
|
|
} else if (auto ap_value =
|
|
MapConstantToAPValue(context, const_inst_id, param_type)) {
|
|
clang::TemplateArgument template_arg(context.ast_context(), param_type,
|
|
*ap_value);
|
|
return clang::TemplateArgumentLoc(template_arg, template_loc);
|
|
}
|
|
}
|
|
|
|
// TODO: Support other types.
|
|
if (diagnose) {
|
|
context.TODO(arg_id,
|
|
"unsupported argument type for non-type template parameter");
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
CARBON_FATAL("Unknown declaration kind for template parameter");
|
|
}
|
|
|
|
auto ConvertArgsToTemplateArgs(Context& context,
|
|
clang::TemplateDecl* template_decl,
|
|
llvm::ArrayRef<SemIR::InstId> arg_ids,
|
|
clang::TemplateArgumentListInfo& arg_list,
|
|
bool diagnose) -> bool {
|
|
clang::SmallVector<clang::TemplateArgument> template_args;
|
|
for (auto* param_decl : template_decl->getTemplateParameters()->asArray()) {
|
|
if (arg_ids.empty()) {
|
|
return true;
|
|
}
|
|
|
|
// A parameter pack consumes all remaining arguments; otherwise, it consumes
|
|
// a single argument.
|
|
// TODO: Handle expanded template parameter packs, which have a known, fixed
|
|
// arity.
|
|
llvm::ArrayRef<SemIR::InstId> args_for_param =
|
|
param_decl->isTemplateParameterPack() ? std::exchange(arg_ids, {})
|
|
: arg_ids.consume_front();
|
|
for (auto [argument_pack_index, arg_id] : llvm::enumerate(args_for_param)) {
|
|
if (auto arg = ConvertArgToTemplateArg(context, template_decl, param_decl,
|
|
arg_id, &template_args,
|
|
argument_pack_index, diagnose)) {
|
|
arg_list.addArgument(*arg);
|
|
template_args.push_back(arg->getArgument());
|
|
argument_pack_index++;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are any remaining arguments, that's an error; convert them to
|
|
// placeholder template arguments so that Clang will diagnose it for us.
|
|
for (auto arg_id : arg_ids) {
|
|
// Synthesize a placeholder `void{}` template argument.
|
|
arg_list.addArgument(MakePlaceholderTemplateArg(context, arg_id));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Given a template and an template argument list, builds a Carbon value
|
|
// describing the corresponding C++ template-id.
|
|
static auto BuildTemplateId(Context& context, SemIR::LocId loc_id,
|
|
clang::SourceLocation loc,
|
|
clang::TemplateDecl* template_decl,
|
|
clang::TemplateArgumentListInfo& arg_list)
|
|
-> SemIR::InstId {
|
|
if (auto* var_template_decl =
|
|
dyn_cast<clang::VarTemplateDecl>(template_decl)) {
|
|
auto decl_result = context.clang_sema().CheckVarTemplateId(
|
|
var_template_decl, /*TemplateLoc=*/clang::SourceLocation(), loc,
|
|
arg_list, /*SetWrittenArgs=*/false);
|
|
return decl_result.isInvalid()
|
|
? SemIR::ErrorInst::InstId
|
|
: ImportCppDecl(context, loc_id,
|
|
SemIR::ClangDeclKey::ForNonFunctionDecl(
|
|
decl_result.get()));
|
|
}
|
|
|
|
if (auto* concept_decl = dyn_cast<clang::ConceptDecl>(template_decl)) {
|
|
auto expr_result = context.clang_sema().CheckConceptTemplateId(
|
|
clang::CXXScopeSpec(), /*TemplateKWLoc=*/clang::SourceLocation(),
|
|
clang::DeclarationNameInfo(concept_decl->getDeclName(), loc),
|
|
concept_decl, concept_decl, &arg_list);
|
|
if (expr_result.isInvalid()) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
auto* expr = expr_result.getAs<clang::ConceptSpecializationExpr>();
|
|
return MakeBoolLiteral(context, loc_id,
|
|
SemIR::BoolValue::From(expr->isSatisfied()));
|
|
}
|
|
|
|
clang::TemplateName template_name(template_decl);
|
|
auto clang_type = context.clang_sema().CheckTemplateIdType(
|
|
clang::ElaboratedTypeKeyword::None, template_name, loc, arg_list,
|
|
/*Scope=*/nullptr, /*ForNestedNameSpecifier=*/false);
|
|
if (clang_type.isNull()) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
return ImportCppType(context, loc_id, clang_type).inst_id;
|
|
}
|
|
|
|
auto PerformCallToCppTemplateName(Context& context, SemIR::LocId loc_id,
|
|
SemIR::ClangDeclId template_decl_id,
|
|
llvm::ArrayRef<SemIR::InstId> arg_ids)
|
|
-> SemIR::InstId {
|
|
auto* template_decl = dyn_cast<clang::TemplateDecl>(
|
|
context.clang_decls().Get(template_decl_id).decl());
|
|
auto loc = GetCppLocation(context, loc_id);
|
|
|
|
// Form a template argument list for this template.
|
|
clang::TemplateArgumentListInfo arg_list(loc, loc);
|
|
if (!ConvertArgsToTemplateArgs(context, template_decl, arg_ids, arg_list)) {
|
|
return SemIR::ErrorInst::InstId;
|
|
}
|
|
|
|
return BuildTemplateId(context, loc_id, loc, template_decl, arg_list);
|
|
}
|
|
|
|
} // namespace Carbon::Check
|