Skip to content

Commit 6cdab78

Browse files
authored
Merge pull request #70867 from xedin/dynamic-enforcement-of-witness-isolation-with-preconcurrency
[TypeChecker/SILGen] Dynamic enforcement of witness/objc isolation with @preconcurrency attribute
2 parents 72ad6c5 + c3a0822 commit 6cdab78

37 files changed

+1002
-94
lines changed

include/swift/AST/ASTBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,7 @@ enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedTypeAttrKind : size_t {
11611161
BridgedTypeAttrKind_Sendable,
11621162
BridgedTypeAttrKind_retroactive,
11631163
BridgedTypeAttrKind_unchecked,
1164+
BridgedTypeAttrKind_preconcurrency,
11641165
BridgedTypeAttrKind__local,
11651166
BridgedTypeAttrKind__noMetadata,
11661167
BridgedTypeAttrKind__opaqueReturnTypeOf,

include/swift/AST/ASTContext.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1282,7 +1282,8 @@ class ASTContext final {
12821282
SourceLoc loc,
12831283
DeclContext *dc,
12841284
ProtocolConformanceState state,
1285-
bool isUnchecked);
1285+
bool isUnchecked,
1286+
bool isPreconcurrency);
12861287

12871288
/// Produce a self-conformance for the given protocol.
12881289
SelfProtocolConformance *

include/swift/AST/Attr.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ TYPE_ATTR(async)
5757
TYPE_ATTR(Sendable)
5858
TYPE_ATTR(retroactive)
5959
TYPE_ATTR(unchecked)
60+
TYPE_ATTR(preconcurrency)
6061
TYPE_ATTR(_local)
6162
TYPE_ATTR(_noMetadata)
6263
TYPE_ATTR(_opaqueReturnTypeOf)

include/swift/AST/Decl.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,11 +1604,15 @@ struct InheritedEntry : public TypeLoc {
16041604
/// Whether there was an @retroactive attribute.
16051605
bool isRetroactive = false;
16061606

1607+
/// Whether there was an @preconcurrency attribute.
1608+
bool isPreconcurrency = false;
1609+
16071610
InheritedEntry(const TypeLoc &typeLoc);
16081611

1609-
InheritedEntry(const TypeLoc &typeLoc, bool isUnchecked, bool isRetroactive)
1610-
: TypeLoc(typeLoc), isUnchecked(isUnchecked), isRetroactive(isRetroactive) {
1611-
}
1612+
InheritedEntry(const TypeLoc &typeLoc, bool isUnchecked, bool isRetroactive,
1613+
bool isPreconcurrency)
1614+
: TypeLoc(typeLoc), isUnchecked(isUnchecked),
1615+
isRetroactive(isRetroactive), isPreconcurrency(isPreconcurrency) {}
16121616
};
16131617

16141618
/// A wrapper for the collection of inherited types for either a `TypeDecl` or

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5628,6 +5628,15 @@ ERROR(unchecked_not_inheritance_clause,none,
56285628
ERROR(unchecked_not_existential,none,
56295629
"'unchecked' attribute cannot apply to non-protocol type %0", (Type))
56305630

5631+
WARNING(preconcurrency_conformance_not_used,none,
5632+
"@preconcurrency attribute on conformance to %0 has no effect", (Type))
5633+
ERROR(preconcurrency_not_inheritance_clause,none,
5634+
"'preconcurrency' attribute only applies in inheritance clauses", ())
5635+
ERROR(preconcurrency_not_existential,none,
5636+
"'preconcurrency' attribute cannot apply to non-protocol type %0", (Type))
5637+
ERROR(preconcurrency_attr_disabled,none,
5638+
"attribute requires '-enable-experimental-feature PreconcurrencyConformances'", ())
5639+
56315640
ERROR(redundant_any_in_existential,none,
56325641
"redundant 'any' in type %0",
56335642
(Type))

include/swift/AST/NameLookup.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,15 @@ struct InheritedNominalEntry : Located<NominalTypeDecl *> {
586586
/// The location of the "unchecked" attribute, if present.
587587
SourceLoc uncheckedLoc;
588588

589+
/// The location of the "preconcurrency" attribute if present.
590+
SourceLoc preconcurrencyLoc;
591+
589592
InheritedNominalEntry() { }
590593

591-
InheritedNominalEntry(
592-
NominalTypeDecl *item, SourceLoc loc,
593-
SourceLoc uncheckedLoc
594-
) : Located(item, loc), uncheckedLoc(uncheckedLoc) { }
594+
InheritedNominalEntry(NominalTypeDecl *item, SourceLoc loc,
595+
SourceLoc uncheckedLoc, SourceLoc preconcurrencyLoc)
596+
: Located(item, loc), uncheckedLoc(uncheckedLoc),
597+
preconcurrencyLoc(preconcurrencyLoc) {}
595598
};
596599

597600
/// Retrieve the set of nominal type declarations that are directly

include/swift/AST/ProtocolConformance.h

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -444,11 +444,11 @@ class NormalProtocolConformance : public RootProtocolConformance,
444444

445445
// Flag bits used in ContextAndBits.
446446
enum {
447-
/// The conformance is invalid.
448-
InvalidFlag = 0x01,
449-
450447
/// The conformance was labeled with @unchecked.
451-
UncheckedFlag = 0x02,
448+
UncheckedFlag = 0x01,
449+
450+
/// The conformance was labeled with @preconcurrency.
451+
PreconcurrencyFlag = 0x02,
452452

453453
/// We have allocated the AssociatedConformances array (but not necessarily
454454
/// populated any of its elements).
@@ -458,10 +458,13 @@ class NormalProtocolConformance : public RootProtocolConformance,
458458
/// The declaration context containing the ExtensionDecl or
459459
/// NominalTypeDecl that declared the conformance.
460460
///
461-
/// Also stores the "invalid", "unchecked" and "has computed associated
461+
/// Also stores the "unchecked", "preconcurrency" and "has computed associated
462462
/// conformances" bits.
463463
llvm::PointerIntPair<DeclContext *, 3, unsigned> ContextAndBits;
464464

465+
/// Indicates whether the conformance is invalid.
466+
bool Invalid : 1;
467+
465468
/// The reason that this conformance exists.
466469
///
467470
/// Either Explicit (e.g. 'struct Foo: Protocol {}' or 'extension Foo:
@@ -501,12 +504,14 @@ class NormalProtocolConformance : public RootProtocolConformance,
501504
public:
502505
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
503506
SourceLoc loc, DeclContext *dc,
504-
ProtocolConformanceState state,
505-
bool isUnchecked)
507+
ProtocolConformanceState state, bool isUnchecked,
508+
bool isPreconcurrency)
506509
: RootProtocolConformance(ProtocolConformanceKind::Normal,
507510
conformingType),
508511
ProtocolAndState(protocol, state), Loc(loc),
509-
ContextAndBits(dc, isUnchecked ? UncheckedFlag : 0) {
512+
ContextAndBits(dc, ((isUnchecked ? UncheckedFlag : 0) |
513+
(isPreconcurrency ? PreconcurrencyFlag : 0))),
514+
Invalid(false) {
510515
assert(!conformingType->hasArchetype() &&
511516
"ProtocolConformances should store interface types");
512517
}
@@ -543,12 +548,12 @@ class NormalProtocolConformance : public RootProtocolConformance,
543548

544549
/// Determine whether this conformance is invalid.
545550
bool isInvalid() const {
546-
return ContextAndBits.getInt() & InvalidFlag;
551+
return Invalid;
547552
}
548553

549554
/// Mark this conformance as invalid.
550555
void setInvalid() {
551-
ContextAndBits.setInt(ContextAndBits.getInt() | InvalidFlag);
556+
Invalid = true;
552557
}
553558

554559
/// Whether this is an "unchecked" conformance.
@@ -563,6 +568,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
563568
ContextAndBits.setInt(ContextAndBits.getInt() | UncheckedFlag);
564569
}
565570

571+
/// Whether this is an preconcurrency conformance.
572+
bool isPreconcurrency() const;
573+
566574
/// Determine whether we've lazily computed the associated conformance array
567575
/// already.
568576
bool hasComputedAssociatedConformances() const {

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ EXPERIMENTAL_FEATURE(GroupActorErrors, true)
270270
// Allow for the 'transferring' keyword to be applied to arguments and results.
271271
EXPERIMENTAL_FEATURE(TransferringArgsAndResults, true)
272272

273+
// Enable `@preconcurrency` attribute on protocol conformances.
274+
EXPERIMENTAL_FEATURE(PreconcurrencyConformances, false)
275+
273276
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
274277
#undef EXPERIMENTAL_FEATURE
275278
#undef UPCOMING_FEATURE

lib/AST/ASTContext.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2491,7 +2491,8 @@ ASTContext::getNormalConformance(Type conformingType,
24912491
SourceLoc loc,
24922492
DeclContext *dc,
24932493
ProtocolConformanceState state,
2494-
bool isUnchecked) {
2494+
bool isUnchecked,
2495+
bool isPreconcurrency) {
24952496
assert(dc->isTypeContext());
24962497

24972498
llvm::FoldingSetNodeID id;
@@ -2505,10 +2506,9 @@ ASTContext::getNormalConformance(Type conformingType,
25052506
return result;
25062507

25072508
// Build a new normal protocol conformance.
2508-
auto result
2509-
= new (*this, AllocationArena::Permanent)
2510-
NormalProtocolConformance(
2511-
conformingType, protocol, loc, dc, state,isUnchecked);
2509+
auto result = new (*this, AllocationArena::Permanent)
2510+
NormalProtocolConformance(conformingType, protocol, loc, dc, state,
2511+
isUnchecked, isPreconcurrency);
25122512
normalConformances.InsertNode(result, insertPos);
25132513

25142514
return result;

lib/AST/ASTPrinter.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2732,6 +2732,8 @@ void PrintAST::printInherited(const Decl *decl) {
27322732
if (inherited.isRetroactive &&
27332733
!llvm::is_contained(Options.ExcludeAttrList, TAK_retroactive))
27342734
Printer << "@retroactive ";
2735+
if (inherited.isPreconcurrency)
2736+
Printer << "@preconcurrency ";
27352737

27362738
printTypeLoc(inherited);
27372739
}, [&]() {
@@ -3906,6 +3908,30 @@ static bool usesFeatureBitwiseCopyable(Decl *decl) { return false; }
39063908

39073909
static bool usesFeatureTransferringArgsAndResults(Decl *decl) { return false; }
39083910

3911+
static bool usesFeaturePreconcurrencyConformances(Decl *decl) {
3912+
auto usesPreconcurrencyConformance = [&](const InheritedTypes &inherited) {
3913+
return llvm::any_of(
3914+
inherited.getEntries(),
3915+
[](const InheritedEntry &entry) { return entry.isPreconcurrency; });
3916+
};
3917+
3918+
if (auto *T = dyn_cast<TypeDecl>(decl))
3919+
return usesPreconcurrencyConformance(T->getInherited());
3920+
3921+
if (auto *E = dyn_cast<ExtensionDecl>(decl)) {
3922+
// If type has `@preconcurrency` conformance(s) all of its
3923+
// extensions have to be guarded by the flag too.
3924+
if (auto *T = dyn_cast<TypeDecl>(E->getExtendedNominal())) {
3925+
if (usesPreconcurrencyConformance(T->getInherited()))
3926+
return true;
3927+
}
3928+
3929+
return usesPreconcurrencyConformance(E->getInherited());
3930+
}
3931+
3932+
return false;
3933+
}
3934+
39093935
/// Suppress the printing of a particular feature.
39103936
static void suppressingFeature(PrintOptions &options, Feature feature,
39113937
llvm::function_ref<void()> action) {
@@ -8317,7 +8343,8 @@ swift::getInheritedForPrinting(
83178343

83188344
Results.push_back({TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
83198345
isUnchecked,
8320-
/*isRetroactive=*/false});
8346+
/*isRetroactive=*/false,
8347+
/*isPreconcurrency=*/false});
83218348
}
83228349
}
83238350

lib/AST/ConformanceLookupTable.cpp

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,19 @@ void ConformanceLookupTable::destroy() {
137137

138138
namespace {
139139
struct ConformanceConstructionInfo : public Located<ProtocolDecl *> {
140-
/// The location of the "unchecked" attribute, if this
140+
/// The location of the "unchecked" attribute, if present.
141141
const SourceLoc uncheckedLoc;
142142

143+
/// The location of the "preconcurrency" attribute if present.
144+
const SourceLoc preconcurrencyLoc;
145+
143146
ConformanceConstructionInfo() { }
144147

145-
ConformanceConstructionInfo(
146-
ProtocolDecl *item, SourceLoc loc,
147-
SourceLoc uncheckedLoc
148-
) : Located(item, loc), uncheckedLoc(uncheckedLoc) { }
148+
ConformanceConstructionInfo(ProtocolDecl *item, SourceLoc loc,
149+
SourceLoc uncheckedLoc,
150+
SourceLoc preconcurrencyLoc)
151+
: Located(item, loc), uncheckedLoc(uncheckedLoc),
152+
preconcurrencyLoc(preconcurrencyLoc) {}
149153
};
150154
}
151155

@@ -200,15 +204,17 @@ void ConformanceLookupTable::forEachInStage(ConformanceStage stage,
200204
loader.first->loadAllConformances(next, loader.second, conformances);
201205
loadAllConformances(next, conformances);
202206
for (auto conf : conformances) {
203-
protocols.push_back({conf->getProtocol(), SourceLoc(), SourceLoc()});
207+
protocols.push_back(
208+
{conf->getProtocol(), SourceLoc(), SourceLoc(), SourceLoc()});
204209
}
205210
} else if (next->getParentSourceFile() ||
206211
next->getParentModule()->isBuiltinModule()) {
207212
bool anyObject = false;
208213
for (const auto &found :
209214
getDirectlyInheritedNominalTypeDecls(next, anyObject)) {
210215
if (auto proto = dyn_cast<ProtocolDecl>(found.Item))
211-
protocols.push_back({proto, found.Loc, found.uncheckedLoc});
216+
protocols.push_back(
217+
{proto, found.Loc, found.uncheckedLoc, found.preconcurrencyLoc});
212218
}
213219
}
214220

@@ -294,15 +300,15 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
294300
addMacroGeneratedProtocols(
295301
nominal, ConformanceSource::forUnexpandedMacro(nominal));
296302
},
297-
[&](ExtensionDecl *ext,
298-
ArrayRef<ConformanceConstructionInfo> protos) {
303+
[&](ExtensionDecl *ext, ArrayRef<ConformanceConstructionInfo> protos) {
299304
// The extension decl may not be validated, so we can't use
300305
// its inherited protocols directly.
301306
auto source = ConformanceSource::forExplicit(ext);
302307
for (auto locAndProto : protos)
303308
addProtocol(
304-
locAndProto.Item, locAndProto.Loc,
305-
source.withUncheckedLoc(locAndProto.uncheckedLoc));
309+
locAndProto.Item, locAndProto.Loc,
310+
source.withUncheckedLoc(locAndProto.uncheckedLoc)
311+
.withPreconcurrencyLoc(locAndProto.preconcurrencyLoc));
306312
});
307313
break;
308314

@@ -495,8 +501,9 @@ void ConformanceLookupTable::addInheritedProtocols(
495501
for (const auto &found :
496502
getDirectlyInheritedNominalTypeDecls(decl, anyObject)) {
497503
if (auto proto = dyn_cast<ProtocolDecl>(found.Item)) {
498-
addProtocol(
499-
proto, found.Loc, source.withUncheckedLoc(found.uncheckedLoc));
504+
addProtocol(proto, found.Loc,
505+
source.withUncheckedLoc(found.uncheckedLoc)
506+
.withPreconcurrencyLoc(found.preconcurrencyLoc));
500507
}
501508
}
502509
}
@@ -953,10 +960,11 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
953960
}
954961

955962
// Create or find the normal conformance.
956-
auto normalConf =
957-
ctx.getNormalConformance(conformingType, protocol, conformanceLoc,
958-
conformingDC, ProtocolConformanceState::Incomplete,
959-
entry->Source.getUncheckedLoc().isValid());
963+
auto normalConf = ctx.getNormalConformance(
964+
conformingType, protocol, conformanceLoc, conformingDC,
965+
ProtocolConformanceState::Incomplete,
966+
entry->Source.getUncheckedLoc().isValid(),
967+
entry->Source.getPreconcurrencyLoc().isValid());
960968
// Invalid code may cause the getConformance call below to loop, so break
961969
// the infinite recursion by setting this eagerly to shortcircuit with the
962970
// early return at the start of this function.

lib/AST/ConformanceLookupTable.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
9191
/// The location of the "unchecked" attribute, if there is one.
9292
SourceLoc uncheckedLoc;
9393

94+
/// The location of the "preconcurrency" attribute, if there is one.
95+
SourceLoc preconcurrencyLoc;
96+
9497
ConformanceSource(void *ptr, ConformanceEntryKind kind)
9598
: Storage(ptr), Kind(kind) { }
9699

@@ -141,6 +144,15 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
141144
return result;
142145
}
143146

147+
/// Return a new conformance source with the given location of
148+
/// "@preconcurrency".
149+
ConformanceSource withPreconcurrencyLoc(SourceLoc preconcurrencyLoc) {
150+
ConformanceSource result(*this);
151+
if (preconcurrencyLoc.isValid())
152+
result.preconcurrencyLoc = preconcurrencyLoc;
153+
return result;
154+
}
155+
144156
/// Retrieve the kind of conformance formed from this source.
145157
ConformanceEntryKind getKind() const { return Kind; }
146158

@@ -184,6 +196,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
184196
return uncheckedLoc;
185197
}
186198

199+
SourceLoc getPreconcurrencyLoc() const {
200+
return preconcurrencyLoc;
201+
}
202+
187203
/// For an inherited conformance, retrieve the class declaration
188204
/// for the inheriting class.
189205
ClassDecl *getInheritingClass() const {

lib/AST/Decl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,7 @@ InheritedEntry::InheritedEntry(const TypeLoc &typeLoc)
15921592
if (auto typeRepr = typeLoc.getTypeRepr()) {
15931593
isUnchecked = typeRepr->findAttrLoc(TAK_unchecked).isValid();
15941594
isRetroactive = typeRepr->findAttrLoc(TAK_retroactive).isValid();
1595+
isPreconcurrency = typeRepr->findAttrLoc(TAK_preconcurrency).isValid();
15951596
}
15961597
}
15971598

0 commit comments

Comments
 (0)