Skip to content

Commit 558380f

Browse files
author
Gabor Horvath
committed
[cxx-interop] Support conditional escapability
This PR adds a variadic macro that builds a SwiftAttr string containing the names of the template type parameters that need to be escapable for the type to be considered escapable. It also adds logic to interpret this annotation. rdar://139065437
1 parent f88b29b commit 558380f

File tree

7 files changed

+156
-24
lines changed

7 files changed

+156
-24
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,10 @@ NOTE(forward_declared_protocol_clashes_with_imported_objc_Swift_protocol, none,
308308
WARNING(return_escapable_with_lifetimebound, none, "the returned type '%0' is annotated as escapable; it cannot have lifetime dependencies", (StringRef))
309309
WARNING(return_nonescapable_without_lifetimebound, none, "the returned type '%0' is annotated as non-escapable; its lifetime dependencies must be annotated", (StringRef))
310310

311+
ERROR(unknown_template_parameter,none,
312+
"template parameter '%0' does not exist", (StringRef))
313+
ERROR(type_template_parameter_expected,none,
314+
"template parameter '%0' expected to be a type parameter", (StringRef))
315+
311316
#define UNDEFINE_DIAGNOSTIC_MACROS
312317
#include "DefineDiagnosticMacros.h"

include/swift/ClangImporter/ClangImporterRequests.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "swift/AST/NameLookup.h"
2323
#include "swift/AST/SimpleRequest.h"
2424
#include "swift/Basic/Statistic.h"
25+
#include "swift/ClangImporter/ClangImporter.h"
2526
#include "clang/AST/Type.h"
2627
#include "llvm/ADT/Hashing.h"
2728
#include "llvm/ADT/TinyPtrVector.h"
@@ -504,14 +505,16 @@ enum class CxxEscapability { Escapable, NonEscapable, Unknown };
504505

505506
struct EscapabilityLookupDescriptor final {
506507
const clang::Type *type;
508+
ClangImporter::Implementation &impl;
509+
bool annotationOnly = true;
507510

508511
friend llvm::hash_code hash_value(const EscapabilityLookupDescriptor &desc) {
509512
return llvm::hash_combine(desc.type);
510513
}
511514

512515
friend bool operator==(const EscapabilityLookupDescriptor &lhs,
513516
const EscapabilityLookupDescriptor &rhs) {
514-
return lhs.type == rhs.type;
517+
return lhs.type == rhs.type && lhs.annotationOnly == rhs.annotationOnly;
515518
}
516519

517520
friend bool operator!=(const EscapabilityLookupDescriptor &lhs,

lib/ClangImporter/ClangImporter.cpp

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
#include "swift/Subsystems.h"
5353
#include "clang/AST/ASTContext.h"
5454
#include "clang/AST/DeclCXX.h"
55+
#include "clang/AST/DeclTemplate.h"
5556
#include "clang/AST/Mangle.h"
57+
#include "clang/AST/TemplateBase.h"
5658
#include "clang/AST/Type.h"
5759
#include "clang/Basic/DiagnosticOptions.h"
5860
#include "clang/Basic/FileEntry.h"
@@ -84,6 +86,7 @@
8486
#include "llvm/ADT/STLExtras.h"
8587
#include "llvm/ADT/SmallVector.h"
8688
#include "llvm/ADT/StringExtras.h"
89+
#include "llvm/ADT/StringRef.h"
8790
#include "llvm/CAS/CASReference.h"
8891
#include "llvm/CAS/ObjectStore.h"
8992
#include "llvm/Support/Casting.h"
@@ -5055,24 +5058,59 @@ TinyPtrVector<ValueDecl *> CXXNamespaceMemberLookup::evaluate(
50555058
CxxEscapability
50565059
ClangTypeEscapability::evaluate(Evaluator &evaluator,
50575060
EscapabilityLookupDescriptor desc) const {
5061+
bool hadUnknown = false;
5062+
auto evaluateEscapability = [&](const clang::Type *type) {
5063+
auto escapability = evaluateOrDefault(
5064+
evaluator,
5065+
ClangTypeEscapability({type, desc.impl, desc.annotationOnly}),
5066+
CxxEscapability::Unknown);
5067+
if (escapability == CxxEscapability::Unknown)
5068+
hadUnknown = true;
5069+
return escapability;
5070+
};
5071+
50585072
auto desugared = desc.type->getUnqualifiedDesugaredType();
50595073
if (const auto *recordType = desugared->getAs<clang::RecordType>()) {
5060-
if (importer::hasNonEscapableAttr(recordType->getDecl()))
5074+
auto recordDecl = recordType->getDecl();
5075+
if (hasNonEscapableAttr(recordDecl))
50615076
return CxxEscapability::NonEscapable;
5062-
if (importer::hasEscapableAttr(recordType->getDecl()))
5077+
if (hasEscapableAttr(recordDecl))
50635078
return CxxEscapability::Escapable;
5064-
auto recordDecl = recordType->getDecl();
5079+
auto conditionalParams =
5080+
importer::getConditionalEscapableAttrParams(recordDecl);
5081+
if (!conditionalParams.empty()) {
5082+
auto specDecl = cast<clang::ClassTemplateSpecializationDecl>(recordDecl);
5083+
auto templateDecl = specDecl->getSpecializedTemplate();
5084+
SmallVector<std::pair<unsigned, StringRef>, 4> argumentsToCheck;
5085+
for (auto [idx, param] :
5086+
llvm::enumerate(*templateDecl->getTemplateParameters())) {
5087+
if (conditionalParams.erase(param->getName()))
5088+
argumentsToCheck.push_back(std::make_pair(idx, param->getName()));
5089+
}
5090+
HeaderLoc loc{recordDecl->getLocation()};
5091+
for (auto name : conditionalParams)
5092+
desc.impl.diagnose(loc, diag::unknown_template_parameter, name);
5093+
5094+
auto &argList = specDecl->getTemplateArgs();
5095+
for (auto argToCheck : argumentsToCheck) {
5096+
auto arg = argList[argToCheck.first];
5097+
if (arg.getKind() != clang::TemplateArgument::Type) {
5098+
desc.impl.diagnose(loc, diag::type_template_parameter_expected,
5099+
argToCheck.second);
5100+
return CxxEscapability::Unknown;
5101+
}
5102+
5103+
auto argEscapability = evaluateEscapability(
5104+
arg.getAsType()->getUnqualifiedDesugaredType());
5105+
if (argEscapability == CxxEscapability::NonEscapable)
5106+
return CxxEscapability::NonEscapable;
5107+
}
5108+
return hadUnknown ? CxxEscapability::Unknown : CxxEscapability::Escapable;
5109+
}
5110+
if (desc.annotationOnly)
5111+
return CxxEscapability::Unknown;
50655112
auto cxxRecordDecl = dyn_cast<clang::CXXRecordDecl>(recordDecl);
50665113
if (!cxxRecordDecl || cxxRecordDecl->isAggregate()) {
5067-
bool hadUnknown = false;
5068-
auto evaluateEscapability = [&](const clang::Type *type) {
5069-
auto escapability = evaluateOrDefault(
5070-
evaluator, ClangTypeEscapability({type}), CxxEscapability::Unknown);
5071-
if (escapability == CxxEscapability::Unknown)
5072-
hadUnknown = true;
5073-
return escapability;
5074-
};
5075-
50765114
if (cxxRecordDecl) {
50775115
for (auto base : cxxRecordDecl->bases()) {
50785116
auto baseEscapability = evaluateEscapability(
@@ -5092,12 +5130,16 @@ ClangTypeEscapability::evaluate(Evaluator &evaluator,
50925130
return hadUnknown ? CxxEscapability::Unknown : CxxEscapability::Escapable;
50935131
}
50945132
}
5133+
if (desc.annotationOnly)
5134+
return CxxEscapability::Unknown;
50955135
if (desugared->isArrayType()) {
50965136
auto elemTy = cast<clang::ArrayType>(desugared)
50975137
->getElementType()
50985138
->getUnqualifiedDesugaredType();
5099-
return evaluateOrDefault(evaluator, ClangTypeEscapability({elemTy}),
5100-
CxxEscapability::Unknown);
5139+
return evaluateOrDefault(
5140+
evaluator,
5141+
ClangTypeEscapability({elemTy, desc.impl, desc.annotationOnly}),
5142+
CxxEscapability::Unknown);
51015143
}
51025144

51035145
// Base cases
@@ -7530,6 +7572,29 @@ bool importer::hasEscapableAttr(const clang::RecordDecl *decl) {
75307572
return hasSwiftAttribute(decl, "Escapable");
75317573
}
75327574

7575+
std::set<StringRef>
7576+
importer::getConditionalEscapableAttrParams(const clang::RecordDecl *decl) {
7577+
std::set<StringRef> result;
7578+
if (!decl->hasAttrs())
7579+
return result;
7580+
for (auto attr : decl->getAttrs()) {
7581+
if (auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr))
7582+
if (swiftAttr->getAttribute().starts_with("escapable_if:")) {
7583+
StringRef params = swiftAttr->getAttribute().drop_front(
7584+
StringRef("escapable_if:").size());
7585+
auto commaPos = params.find(',');
7586+
StringRef nextParam = params.take_front(commaPos);
7587+
while (!nextParam.empty() && commaPos != StringRef::npos) {
7588+
result.insert(nextParam.trim());
7589+
params = params.drop_front(nextParam.size() + 1);
7590+
commaPos = params.find(',');
7591+
nextParam = params.take_front(commaPos);
7592+
}
7593+
}
7594+
}
7595+
return result;
7596+
}
7597+
75337598
/// Recursively checks that there are no pointers in any fields or base classes.
75347599
/// Does not check C++ records with specific API annotations.
75357600
static bool hasPointerInSubobjects(const clang::CXXRecordDecl *decl) {

lib/ClangImporter/ImportDecl.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,7 +2186,10 @@ namespace {
21862186
}
21872187

21882188
if (Impl.SwiftContext.LangOpts.hasFeature(Feature::NonescapableTypes) &&
2189-
importer::hasNonEscapableAttr(decl)) {
2189+
evaluateOrDefault(
2190+
Impl.SwiftContext.evaluator,
2191+
ClangTypeEscapability({decl->getTypeForDecl(), Impl}),
2192+
CxxEscapability::Unknown) == CxxEscapability::NonEscapable) {
21902193
result->getAttrs().add(new (Impl.SwiftContext)
21912194
NonEscapableAttr(/*Implicit=*/true));
21922195
}
@@ -3930,7 +3933,11 @@ namespace {
39303933
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
39313934
// Assume default constructed view types have no dependencies.
39323935
if (ctordecl->isDefaultConstructor() &&
3933-
importer::hasNonEscapableAttr(ctordecl->getParent()))
3936+
evaluateOrDefault(
3937+
Impl.SwiftContext.evaluator,
3938+
ClangTypeEscapability(
3939+
{ctordecl->getParent()->getTypeForDecl(), Impl}),
3940+
CxxEscapability::Unknown) == CxxEscapability::NonEscapable)
39343941
lifetimeDependencies.push_back(immortalLifetime);
39353942
}
39363943
if (lifetimeDependencies.empty()) {
@@ -8240,8 +8247,10 @@ bool swift::importer::isMutabilityAttr(const clang::SwiftAttrAttr *swiftAttr) {
82408247
swiftAttr->getAttribute() == "nonmutating";
82418248
}
82428249

8243-
static bool importAsUnsafe(ASTContext &context, const clang::NamedDecl *decl,
8250+
static bool importAsUnsafe(ClangImporter::Implementation &impl,
8251+
const clang::NamedDecl *decl,
82448252
const Decl *MappedDecl) {
8253+
auto &context = impl.SwiftContext;
82458254
if (!context.LangOpts.hasFeature(Feature::SafeInterop) ||
82468255
!context.LangOpts.hasFeature(Feature::AllowUnsafeAttribute))
82478256
return false;
@@ -8255,10 +8264,10 @@ static bool importAsUnsafe(ASTContext &context, const clang::NamedDecl *decl,
82558264
return false;
82568265

82578266
if (const auto *record = dyn_cast<clang::RecordDecl>(decl))
8258-
return evaluateOrDefault(context.evaluator,
8259-
ClangTypeEscapability({record->getTypeForDecl()}),
8260-
CxxEscapability::Unknown) ==
8261-
CxxEscapability::Unknown;
8267+
return evaluateOrDefault(
8268+
context.evaluator,
8269+
ClangTypeEscapability({record->getTypeForDecl(), impl, false}),
8270+
CxxEscapability::Unknown) == CxxEscapability::Unknown;
82628271

82638272
return false;
82648273
}
@@ -8440,7 +8449,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
84408449
}
84418450
}
84428451

8443-
if (seenUnsafe || importAsUnsafe(SwiftContext, ClangDecl, MappedDecl)) {
8452+
if (seenUnsafe || importAsUnsafe(*this, ClangDecl, MappedDecl)) {
84448453
auto attr = new (SwiftContext) UnsafeAttr(/*implicit=*/!seenUnsafe);
84458454
MappedDecl->getAttrs().add(attr);
84468455
}

lib/ClangImporter/ImporterImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,9 @@ bool hasNonEscapableAttr(const clang::RecordDecl *decl);
20422042

20432043
bool hasEscapableAttr(const clang::RecordDecl *decl);
20442044

2045+
std::set<StringRef>
2046+
getConditionalEscapableAttrParams(const clang::RecordDecl *decl);
2047+
20452048
bool isViewType(const clang::CXXRecordDecl *decl);
20462049

20472050
} // end namespace importer

lib/ClangImporter/SwiftBridging/swift/bridging

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939

4040
#define _CXX_INTEROP_STRINGIFY(_x) #_x
4141

42+
#define _CXX_INTEROP_CONCAT_(a,b,c,d,e,f,g,i,j,k,l,m,n,o,p,...) \
43+
#a "," #b "," #c "," #d "," #e "," #f "," #g "," #i "," #j "," #k "," \
44+
#l "," #m "," #n "," #o "," #p
45+
#define _CXX_INTEROP_CONCAT(...) \
46+
_CXX_INTEROP_CONCAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,)
47+
4248
/// Specifies that a C++ `class` or `struct` is reference-counted using
4349
/// the given `retain` and `release` functions. This annotation lets Swift import
4450
/// such a type as reference counted type in Swift, taking advantage of Swift's
@@ -172,6 +178,11 @@
172178
#define SWIFT_ESCAPABLE \
173179
__attribute__((swift_attr("Escapable")))
174180

181+
/// Specifies that a C++ `class` or `struct` should be imported as a escapable
182+
/// Swift value if all of the specified template arguments are escapable.
183+
#define SWIFT_ESCAPABLE_IF(...) \
184+
__attribute__((swift_attr("escapable_if:" _CXX_INTEROP_CONCAT(__VA_ARGS__))))
185+
175186
/// Specifies that the return value is passed as owned for C++ functions and
176187
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
177188
#define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained")))
@@ -196,6 +207,7 @@
196207
#define SWIFT_NONCOPYABLE
197208
#define SWIFT_NONESCAPABLE
198209
#define SWIFT_ESCAPABLE
210+
#define SWIFT_ESCAPABLE_IF(...)
199211
#define SWIFT_RETURNS_RETAINED
200212
#define SWIFT_RETURNS_UNRETAINED
201213

test/Interop/Cxx/class/nonescapable-errors.swift

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ View g(int* x) {
3737
return View(x);
3838
}
3939

40+
template<typename F, typename S>
41+
struct SWIFT_ESCAPABLE_IF(F, S) MyPair {
42+
F first;
43+
S second;
44+
};
45+
46+
MyPair<View, Owner> h1(int* x);
47+
MyPair<Owner, View> h2(int* x);
48+
MyPair<Owner, Owner> h3(int* x);
49+
50+
template<typename F, typename S>
51+
struct SWIFT_ESCAPABLE_IF(F, Missing) MyPair2 {
52+
F first;
53+
S second;
54+
};
55+
56+
template<typename F, int S>
57+
struct SWIFT_ESCAPABLE_IF(F, S) MyType {
58+
F field;
59+
};
60+
61+
MyPair2<Owner, Owner> i1();
62+
MyType<Owner, 0> i2();
63+
4064
//--- test.swift
4165

4266
import Test
@@ -50,7 +74,18 @@ public func noAnnotations() -> View {
5074
// CHECK-NOT: nonescapable.h:19
5175
f2(nil, nil)
5276
// CHECK: nonescapable.h:23:6: warning: the returned type 'View' is annotated as non-escapable; its lifetime dependencies must be annotated
53-
// CHECKL nonescapable.h:23:6: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
77+
// CHECK: nonescapable.h:23:6: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
5478
g(nil)
79+
h1(nil)
80+
// CHECK: nonescapable.h:33:21: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
81+
h2(nil)
82+
// CHECK: nonescapable.h:34:21: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
83+
h3(nil)
84+
i1()
85+
// CHECK: nonescapable.h:38:39: error: template parameter 'Missing' does not exist
86+
i2()
87+
// CHECK: nonescapable.h:44:33: error: template parameter 'S' expected to be a type parameter
88+
// CHECK-NOT: error
89+
// CHECK-NOT: warning
5590
return View()
5691
}

0 commit comments

Comments
 (0)