Skip to content

Commit 5b2f3b7

Browse files
authored
Merge pull request #77588 from swiftlang/gaborh/conditional-escapability
[cxx-interop] Support conditional escapability
2 parents e7a8290 + 558380f commit 5b2f3b7

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)