Skip to content

Commit d14281f

Browse files
committed
[cxx-interop] Allow function templates with defaulted template type parameters to be called.
If a defaulted template type parameter is not used in the function's signature, don't create a corresponding generic argument for that template type. This allows us to call function templates with defaulted template type parameters. This is very common in the standard library for things like enable_if which is used to disable various functions/overloads with SFINAE. The biggest part of this change is going forward not all function templates will be imported as generic functions in Swift. This should work OK but we may discover there was some logic which only looked for generic function when dealing with function templates.
1 parent 0b38780 commit d14281f

7 files changed

+186
-6
lines changed

lib/AST/ClangTypeConverter.cpp

+7-1
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,6 @@ ClangTypeConverter::getClangTemplateArguments(
865865
ArrayRef<Type> genericArgs,
866866
SmallVectorImpl<clang::TemplateArgument> &templateArgs) {
867867
assert(templateArgs.size() == 0);
868-
assert(genericArgs.size() == templateParams->size());
869868

870869
// Keep track of the types we failed to convert so we can return a useful
871870
// error.
@@ -874,6 +873,13 @@ ClangTypeConverter::getClangTemplateArguments(
874873
// Note: all template parameters must be template type parameters. This is
875874
// verified when we import the Clang decl.
876875
auto templateParam = cast<clang::TemplateTypeParmDecl>(param);
876+
// We must have found a defaulted parameter at the end of the list.
877+
if (templateParam->getIndex() >= genericArgs.size()) {
878+
templateArgs.push_back(
879+
clang::TemplateArgument(templateParam->getDefaultArgument()));
880+
continue;
881+
}
882+
877883
auto replacement = genericArgs[templateParam->getIndex()];
878884
auto qualType = convert(replacement);
879885
if (qualType.isNull()) {

lib/ClangImporter/ImportDecl.cpp

+43-2
Original file line numberDiff line numberDiff line change
@@ -4087,6 +4087,30 @@ namespace {
40874087
DeclName name = accessorInfo ? DeclName() : importedName.getDeclName();
40884088
auto selfIdx = importedName.getSelfIndex();
40894089

4090+
auto underlyingTypeIsSame = [](const clang::Type *a,
4091+
clang::TemplateTypeParmDecl *b) {
4092+
while (a->isPointerType() || a->isReferenceType())
4093+
a = a->getPointeeType().getTypePtr();
4094+
return a == b->getTypeForDecl();
4095+
};
4096+
4097+
auto templateParamTypeUsedInSignature =
4098+
[underlyingTypeIsSame,
4099+
decl](clang::TemplateTypeParmDecl *type) -> bool {
4100+
// TODO(SR-13809): we will want to update this to handle dependent
4101+
// types when those are supported.
4102+
if (underlyingTypeIsSame(decl->getReturnType().getTypePtr(), type))
4103+
return true;
4104+
4105+
for (unsigned i : range(0, decl->getNumParams())) {
4106+
if (underlyingTypeIsSame(
4107+
decl->getParamDecl(i)->getType().getTypePtr(), type))
4108+
return true;
4109+
}
4110+
4111+
return false;
4112+
};
4113+
40904114
ImportedType importedType;
40914115
bool selfIsInOut = false;
40924116
ParameterList *bodyParams = nullptr;
@@ -4095,15 +4119,32 @@ namespace {
40954119
if (funcTemplate) {
40964120
unsigned i = 0;
40974121
for (auto param : *funcTemplate->getTemplateParameters()) {
4122+
auto templateTypeParam = cast<clang::TemplateTypeParmDecl>(param);
4123+
// If the template type parameter isn't used in the signature then we
4124+
// won't be able to deduce what it is when the function template is
4125+
// called in Swift code. This is OK if there's a defaulted type we can
4126+
// use (in which case we just don't add a correspond generic). This
4127+
// also means sometimes we will import a function template as a
4128+
// "normal" (non-generic) Swift function.
4129+
//
4130+
// If the defaulted template type parameter is used in the signature,
4131+
// then still add a generic so that it can be overrieded.
4132+
// TODO(SR-14837): in the future we might want to import two overloads
4133+
// in this case so that the default type could still be used.
4134+
if (templateTypeParam->hasDefaultArgument() &&
4135+
!templateParamTypeUsedInSignature(templateTypeParam))
4136+
continue;
4137+
40984138
auto *typeParam = Impl.createDeclWithClangNode<GenericTypeParamDecl>(
40994139
param, AccessLevel::Public, dc,
41004140
Impl.SwiftContext.getIdentifier(param->getName()), SourceLoc(), 0,
41014141
i);
41024142
templateParams.push_back(typeParam);
41034143
(void)++i;
41044144
}
4105-
genericParams = GenericParamList::create(Impl.SwiftContext, SourceLoc(),
4106-
templateParams, SourceLoc());
4145+
if (!templateParams.empty())
4146+
genericParams = GenericParamList::create(
4147+
Impl.SwiftContext, SourceLoc(), templateParams, SourceLoc());
41074148
}
41084149

41094150
if (!dc->isModuleScopeContext() && !isa<clang::CXXMethodDecl>(decl)) {

lib/Sema/CSApply.cpp

+7-3
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,14 @@ Solution::computeSubstitutions(GenericSignature sig,
112112
static ConcreteDeclRef generateDeclRefForSpecializedCXXFunctionTemplate(
113113
ASTContext &ctx, AbstractFunctionDecl *oldDecl, SubstitutionMap subst,
114114
clang::FunctionDecl *specialized) {
115+
FunctionType *newFnType = nullptr;
115116
// Create a new ParameterList with the substituted type.
116-
auto oldFnType =
117-
cast<GenericFunctionType>(oldDecl->getInterfaceType().getPointer());
118-
auto newFnType = oldFnType->substGenericArgs(subst);
117+
if (auto oldFnType = dyn_cast<GenericFunctionType>(
118+
oldDecl->getInterfaceType().getPointer())) {
119+
newFnType = oldFnType->substGenericArgs(subst);
120+
} else {
121+
newFnType = cast<FunctionType>(oldDecl->getInterfaceType().getPointer());
122+
}
119123
// The constructor type is a function type as follows:
120124
// (CType.Type) -> (Generic) -> CType
121125
// And a method's function type is as follows:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#ifndef TEST_INTEROP_CXX_TEMPLATES_INPUTS_DEFAULTED_TEMPLATE_TYPE_PARAMETER_H
2+
#define TEST_INTEROP_CXX_TEMPLATES_INPUTS_DEFAULTED_TEMPLATE_TYPE_PARAMETER_H
3+
4+
template <class>
5+
struct ClassTemplate {};
6+
7+
struct X {
8+
enum class CtorPicked { dependent, arg, empty } picked;
9+
10+
// Make sure we don't crash for dependent types.
11+
template <class T = void>
12+
X(ClassTemplate<T>) : picked(CtorPicked::dependent) {}
13+
14+
template <class T = void>
15+
X(T) : picked(CtorPicked::arg) {}
16+
17+
template <class = void>
18+
X() : picked(CtorPicked::empty) {}
19+
};
20+
21+
template <class = void>
22+
void defaultedTemplateTypeParam() {}
23+
24+
template <class T = void>
25+
void defaultedTemplateTypeParamUsedInArgs(T) {}
26+
27+
template <class T = void>
28+
T defaultedTemplateTypeParamUsedInReturn() {
29+
return 0;
30+
}
31+
32+
template <class T = void>
33+
void defaultedTemplateTypeParamAndDefaultedParam(T = 0) {}
34+
35+
template <class T>
36+
void functionTemplateWithDefaultedParam(T = 0) {}
37+
38+
template <class T = void>
39+
void defaultedTemplateTypeParamUsedInSignatureAndUnrealtedParam(int, T) {}
40+
41+
template <class = void>
42+
void defaultedTemplateTypeParamAndUnrealtedParam(int) {}
43+
44+
template <class T = int>
45+
void overloadedDefaultedTemplate(T) {}
46+
47+
void overloadedDefaultedTemplate(int) {}
48+
49+
template <typename T = int>
50+
void defaultedTemplateReferenceTypeParam(T &t) {}
51+
52+
template <typename T = int>
53+
void defaultedTemplatePointerTypeParam(T *t) {}
54+
55+
template <typename T = int>
56+
void defaultedTemplatePointerReferenceTypeParam(T *&t) {}
57+
58+
template <typename T = int>
59+
void defaultedTemplatePointerPointerTypeParam(T **t) {}
60+
61+
#endif // TEST_INTEROP_CXX_TEMPLATES_INPUTS_DEFAULTED_TEMPLATE_TYPE_PARAMETER_H

test/Interop/Cxx/templates/Inputs/module.modulemap

+5
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,8 @@ module LargeClassTemplates {
117117
header "large-class-templates.h"
118118
requires cplusplus
119119
}
120+
121+
module DefaultedTemplateTypeParameter {
122+
header "defaulted-template-type-parameter.h"
123+
requires cplusplus
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-ide-test -print-module -module-to-print=DefaultedTemplateTypeParameter -I %S/Inputs -source-filename=x -enable-cxx-interop | %FileCheck %s
2+
3+
// CHECK: struct X {
4+
// CHECK: init<T>(_: T)
5+
// CHECK: init()
6+
// CHECK: }
7+
8+
// CHECK: func defaultedTemplateTypeParam()
9+
// CHECK: func defaultedTemplateTypeParamUsedInArgs<T>(_: T)
10+
// CHECK: func defaultedTemplateTypeParamUsedInReturn<T>() -> T
11+
// CHECK: func defaultedTemplateTypeParamAndDefaultedParam<T>(_: T)
12+
// CHECK: func functionTemplateWithDefaultedParam<T>(_: T)
13+
// CHECK: func defaultedTemplateTypeParamUsedInSignatureAndUnrealtedParam<T>(_: Int32, _: T)
14+
// CHECK: func defaultedTemplateTypeParamAndUnrealtedParam(_: Int32)
15+
// CHECK: func overloadedDefaultedTemplate<T>(_: T)
16+
// CHECK: func overloadedDefaultedTemplate(_: Int32)
17+
// CHECK: func defaultedTemplateReferenceTypeParam<T>(_ t: UnsafeMutablePointer<T>)
18+
// The following types aren't imported correctly, but that does not have to do
19+
// with the fact that the template type paramaters are defaulted.
20+
// CHECK: func defaultedTemplatePointerTypeParam<T>(_ t: OpaquePointer!)
21+
// CHECK: func defaultedTemplatePointerReferenceTypeParam<T>(_ t: UnsafeMutablePointer<OpaquePointer?>)
22+
// CHECK: func defaultedTemplatePointerPointerTypeParam<T>(_ t: UnsafeMutablePointer<OpaquePointer?>!)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop)
2+
//
3+
// REQUIRES: executable_test
4+
5+
import DefaultedTemplateTypeParameter
6+
import StdlibUnittest
7+
8+
// The purpose of this test is to make sure that we correctly IRGen these
9+
// templates and link them. The behavior is not important here (we test that
10+
// elsewhere).
11+
var DefaultedTemplateTestSuite = TestSuite("Defaulted Template Type Parameters")
12+
13+
DefaultedTemplateTestSuite.test("Correct ctor picked") {
14+
let x1 = X(0)
15+
expectEqual(x1.picked, .arg)
16+
17+
let x2 = X()
18+
expectEqual(x2.picked, .empty)
19+
}
20+
21+
DefaultedTemplateTestSuite.test("Function with defaulted template type parameters") {
22+
defaultedTemplateTypeParam()
23+
defaultedTemplateTypeParamUsedInArgs(0)
24+
let _: Int = defaultedTemplateTypeParamUsedInReturn()
25+
defaultedTemplateTypeParamAndDefaultedParam(0)
26+
functionTemplateWithDefaultedParam(0)
27+
defaultedTemplateTypeParamUsedInSignatureAndUnrealtedParam(0, 0)
28+
defaultedTemplateTypeParamAndUnrealtedParam(0)
29+
}
30+
31+
DefaultedTemplateTestSuite.test("Overloaded function template is not ambiguous") {
32+
overloadedDefaultedTemplate(X())
33+
overloadedDefaultedTemplate(0)
34+
}
35+
36+
DefaultedTemplateTestSuite.test("Pointer types") {
37+
var x = 0
38+
defaultedTemplateReferenceTypeParam(&x)
39+
}
40+
41+
runAllTests()

0 commit comments

Comments
 (0)