Skip to content

Commit 29f5d7a

Browse files
committed
[SE-0302] Implement '@unchecked Sendable' syntax.
Parse and provide semantic checking for '@unchecked Sendable', for a Sendable conformance that doesn't perform additional semantic checks for correctness. Part of rdar://78269000.
1 parent 2894f05 commit 29f5d7a

24 files changed

+200
-71
lines changed

include/swift/AST/ASTContext.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,8 @@ class ASTContext final {
10191019
ProtocolDecl *protocol,
10201020
SourceLoc loc,
10211021
DeclContext *dc,
1022-
ProtocolConformanceState state);
1022+
ProtocolConformanceState state,
1023+
bool isUnchecked);
10231024

10241025
/// Produce a self-conformance for the given protocol.
10251026
SelfProtocolConformance *

include/swift/AST/Attr.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ TYPE_ATTR(differentiable)
5555
TYPE_ATTR(noDerivative)
5656
TYPE_ATTR(async)
5757
TYPE_ATTR(Sendable)
58+
TYPE_ATTR(unchecked)
5859

5960
// SIL-specific attributes
6061
TYPE_ATTR(block_storage)

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4516,16 +4516,23 @@ ERROR(concurrent_value_class_mutable_property,none,
45164516
(DeclName, DescriptiveDeclKind, DeclName))
45174517
ERROR(concurrent_value_outside_source_file,none,
45184518
"conformance to 'Sendable' must occur in the same source file as "
4519-
"%0 %1; use 'UnsafeSendable' for retroactive conformance",
4519+
"%0 %1; use '@unchecked Sendable' for retroactive conformance",
45204520
(DescriptiveDeclKind, DeclName))
45214521
ERROR(concurrent_value_nonfinal_class,none,
45224522
"non-final class %0 cannot conform to `Sendable`; "
4523-
"use `UnsafeSendable`", (DeclName))
4523+
"use `@unchecked Sendable`", (DeclName))
45244524
ERROR(concurrent_value_inherit,none,
45254525
"`Sendable` class %1 cannot inherit from another class"
45264526
"%select{| other than 'NSObject'}0",
45274527
(bool, DeclName))
45284528

4529+
WARNING(unchecked_conformance_not_special,none,
4530+
"@unchecked conformance to %0 has no meaning", (Type))
4531+
ERROR(unchecked_not_inheritance_clause,none,
4532+
"'unchecked' attribute only applies in inheritance clauses", ())
4533+
ERROR(unchecked_not_existential,none,
4534+
"'unchecked' attribute cannot apply to non-protocol type %0", (Type))
4535+
45294536
ERROR(nonisolated_let,none,
45304537
"'nonisolated' is meaningless on 'let' declarations because "
45314538
"they are immutable",

include/swift/AST/NameLookup.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,19 @@ void filterForDiscriminator(SmallVectorImpl<Result> &results,
503503

504504
} // end namespace namelookup
505505

506+
/// Describes an inherited nominal entry.
507+
struct InheritedNominalEntry : Located<NominalTypeDecl *> {
508+
/// The location of the "unchecked" attribute, if present.
509+
SourceLoc uncheckedLoc;
510+
511+
InheritedNominalEntry() { }
512+
513+
InheritedNominalEntry(
514+
NominalTypeDecl *item, SourceLoc loc,
515+
SourceLoc uncheckedLoc
516+
) : Located(item, loc), uncheckedLoc(uncheckedLoc) { }
517+
};
518+
506519
/// Retrieve the set of nominal type declarations that are directly
507520
/// "inherited" by the given declaration at a particular position in the
508521
/// list of "inherited" types.
@@ -511,15 +524,15 @@ void filterForDiscriminator(SmallVectorImpl<Result> &results,
511524
/// AnyObject type, set \c anyObject true.
512525
void getDirectlyInheritedNominalTypeDecls(
513526
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
514-
unsigned i, llvm::SmallVectorImpl<Located<NominalTypeDecl *>> &result,
527+
unsigned i, llvm::SmallVectorImpl<InheritedNominalEntry> &result,
515528
bool &anyObject);
516529

517530
/// Retrieve the set of nominal type declarations that are directly
518531
/// "inherited" by the given declaration, looking through typealiases
519532
/// and splitting out the components of compositions.
520533
///
521534
/// If we come across the AnyObject type, set \c anyObject true.
522-
SmallVector<Located<NominalTypeDecl *>, 4> getDirectlyInheritedNominalTypeDecls(
535+
SmallVector<InheritedNominalEntry, 4> getDirectlyInheritedNominalTypeDecls(
523536
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
524537
bool &anyObject);
525538

include/swift/AST/ProtocolConformance.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -412,11 +412,20 @@ class NormalProtocolConformance : public RootProtocolConformance,
412412
/// The location of this protocol conformance in the source.
413413
SourceLoc Loc;
414414

415+
// Flag bits used in ContextAndBits.
416+
enum {
417+
/// The conformance is invalid.
418+
InvalidFlag = 0x01,
419+
420+
/// The conformance was labeled with @unchecked.
421+
UncheckedFlag = 0x02,
422+
};
423+
415424
/// The declaration context containing the ExtensionDecl or
416425
/// NominalTypeDecl that declared the conformance.
417426
///
418-
/// Also stores the "invalid" bit.
419-
llvm::PointerIntPair<DeclContext *, 1, bool> ContextAndInvalid;
427+
/// Also stores the "invalid" and "unchecked" bits.
428+
llvm::PointerIntPair<DeclContext *, 2, unsigned> ContextAndBits;
420429

421430
/// The reason that this conformance exists.
422431
///
@@ -457,11 +466,12 @@ class NormalProtocolConformance : public RootProtocolConformance,
457466
public:
458467
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
459468
SourceLoc loc, DeclContext *dc,
460-
ProtocolConformanceState state)
469+
ProtocolConformanceState state,
470+
bool isUnchecked)
461471
: RootProtocolConformance(ProtocolConformanceKind::Normal,
462472
conformingType),
463473
ProtocolAndState(protocol, state), Loc(loc),
464-
ContextAndInvalid(dc, false) {
474+
ContextAndBits(dc, isUnchecked ? UncheckedFlag : 0) {
465475
assert(!conformingType->hasArchetype() &&
466476
"ProtocolConformances should store interface types");
467477
}
@@ -475,7 +485,7 @@ class NormalProtocolConformance : public RootProtocolConformance,
475485
/// Get the declaration context that contains the conforming extension or
476486
/// nominal type declaration.
477487
DeclContext *getDeclContext() const {
478-
return ContextAndInvalid.getPointer();
488+
return ContextAndBits.getPointer();
479489
}
480490

481491
/// Get any additional requirements that are required for this conformance to
@@ -497,15 +507,20 @@ class NormalProtocolConformance : public RootProtocolConformance,
497507

498508
/// Determine whether this conformance is invalid.
499509
bool isInvalid() const {
500-
return ContextAndInvalid.getInt();
510+
return ContextAndBits.getInt() & InvalidFlag;
501511
}
502512

503513
/// Mark this conformance as invalid.
504514
void setInvalid() {
505-
ContextAndInvalid.setInt(true);
515+
ContextAndBits.setInt(ContextAndBits.getInt() | InvalidFlag);
506516
SignatureConformances = {};
507517
}
508518

519+
/// Whether this is an "unchecked" conformance.
520+
bool isUnchecked() const {
521+
return ContextAndBits.getInt() & UncheckedFlag;
522+
}
523+
509524
/// Get the kind of source from which this conformance comes.
510525
ConformanceEntryKind getSourceKind() const {
511526
return SourceKindAndImplyingConformance.getInt();

lib/AST/ASTContext.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,7 +2188,8 @@ ASTContext::getConformance(Type conformingType,
21882188
ProtocolDecl *protocol,
21892189
SourceLoc loc,
21902190
DeclContext *dc,
2191-
ProtocolConformanceState state) {
2191+
ProtocolConformanceState state,
2192+
bool isUnchecked) {
21922193
assert(dc->isTypeContext());
21932194

21942195
llvm::FoldingSetNodeID id;
@@ -2204,7 +2205,8 @@ ASTContext::getConformance(Type conformingType,
22042205
// Build a new normal protocol conformance.
22052206
auto result
22062207
= new (*this, AllocationArena::Permanent)
2207-
NormalProtocolConformance(conformingType, protocol, loc, dc, state);
2208+
NormalProtocolConformance(
2209+
conformingType, protocol, loc, dc, state,isUnchecked);
22082210
normalConformances.InsertNode(result, insertPos);
22092211

22102212
return result;

lib/AST/ConformanceLookupTable.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,17 @@ void ConformanceLookupTable::destroy() {
141141
}
142142

143143
namespace {
144-
using ConformanceConstructionInfo = Located<ProtocolDecl *>;
144+
struct ConformanceConstructionInfo : public Located<ProtocolDecl *> {
145+
/// The location of the "unchecked" attribute, if this
146+
const SourceLoc uncheckedLoc;
147+
148+
ConformanceConstructionInfo() { }
149+
150+
ConformanceConstructionInfo(
151+
ProtocolDecl *item, SourceLoc loc,
152+
SourceLoc uncheckedLoc
153+
) : Located(item, loc), uncheckedLoc(uncheckedLoc) { }
154+
};
145155
}
146156

147157
template<typename NominalFunc, typename ExtensionFunc>
@@ -195,14 +205,14 @@ void ConformanceLookupTable::forEachInStage(ConformanceStage stage,
195205
loader.first->loadAllConformances(next, loader.second, conformances);
196206
loadAllConformances(next, conformances);
197207
for (auto conf : conformances) {
198-
protocols.push_back({conf->getProtocol(), SourceLoc()});
208+
protocols.push_back({conf->getProtocol(), SourceLoc(), SourceLoc()});
199209
}
200210
} else if (next->getParentSourceFile()) {
201211
bool anyObject = false;
202212
for (const auto &found :
203213
getDirectlyInheritedNominalTypeDecls(next, anyObject)) {
204214
if (auto proto = dyn_cast<ProtocolDecl>(found.Item))
205-
protocols.push_back({proto, found.Loc});
215+
protocols.push_back({proto, found.Loc, found.uncheckedLoc});
206216
}
207217
}
208218

@@ -282,7 +292,9 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
282292
// its inherited protocols directly.
283293
auto source = ConformanceSource::forExplicit(ext);
284294
for (auto locAndProto : protos)
285-
addProtocol(locAndProto.Item, locAndProto.Loc, source);
295+
addProtocol(
296+
locAndProto.Item, locAndProto.Loc,
297+
source.withUncheckedLoc(locAndProto.uncheckedLoc));
286298
});
287299
break;
288300

@@ -471,8 +483,10 @@ void ConformanceLookupTable::addInheritedProtocols(
471483
bool anyObject = false;
472484
for (const auto &found :
473485
getDirectlyInheritedNominalTypeDecls(decl, anyObject)) {
474-
if (auto proto = dyn_cast<ProtocolDecl>(found.Item))
475-
addProtocol(proto, found.Loc, source);
486+
if (auto proto = dyn_cast<ProtocolDecl>(found.Item)) {
487+
addProtocol(
488+
proto, found.Loc, source.withUncheckedLoc(found.uncheckedLoc));
489+
}
476490
}
477491
}
478492

@@ -843,7 +857,8 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
843857

844858
auto normalConf =
845859
ctx.getConformance(conformingType, protocol, conformanceLoc,
846-
conformingDC, ProtocolConformanceState::Incomplete);
860+
conformingDC, ProtocolConformanceState::Incomplete,
861+
entry->Source.getUncheckedLoc().isValid());
847862
// Invalid code may cause the getConformance call below to loop, so break
848863
// the infinite recursion by setting this eagerly to shortcircuit with the
849864
// 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
@@ -85,6 +85,9 @@ class ConformanceLookupTable {
8585
class ConformanceSource {
8686
llvm::PointerIntPair<void *, 2, ConformanceEntryKind> Storage;
8787

88+
/// The location of the "unchecked" attribute, if there is one.
89+
SourceLoc uncheckedLoc;
90+
8891
ConformanceSource(void *ptr, ConformanceEntryKind kind)
8992
: Storage(ptr, kind) { }
9093

@@ -123,6 +126,14 @@ class ConformanceLookupTable {
123126
return ConformanceSource(typeDecl, ConformanceEntryKind::Synthesized);
124127
}
125128

129+
/// Return a new conformance source with the given location of "@unchecked".
130+
ConformanceSource withUncheckedLoc(SourceLoc uncheckedLoc) {
131+
ConformanceSource result(*this);
132+
if (uncheckedLoc.isValid())
133+
result.uncheckedLoc = uncheckedLoc;
134+
return result;
135+
}
136+
126137
/// Retrieve the kind of conformance formed from this source.
127138
ConformanceEntryKind getKind() const { return Storage.getInt(); }
128139

@@ -149,6 +160,11 @@ class ConformanceLookupTable {
149160
llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
150161
}
151162

163+
/// The location of the @unchecked attribute, if any.
164+
SourceLoc getUncheckedLoc() const {
165+
return uncheckedLoc;
166+
}
167+
152168
/// For an inherited conformance, retrieve the class declaration
153169
/// for the inheriting class.
154170
ClassDecl *getInheritingClass() const {

lib/AST/NameLookup.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2662,7 +2662,7 @@ CustomAttrNominalRequest::evaluate(Evaluator &evaluator,
26622662

26632663
void swift::getDirectlyInheritedNominalTypeDecls(
26642664
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
2665-
unsigned i, llvm::SmallVectorImpl<Located<NominalTypeDecl *>> &result,
2665+
unsigned i, llvm::SmallVectorImpl<InheritedNominalEntry> &result,
26662666
bool &anyObject) {
26672667
auto typeDecl = decl.dyn_cast<const TypeDecl *>();
26682668
auto extDecl = decl.dyn_cast<const ExtensionDecl *>();
@@ -2684,18 +2684,28 @@ void swift::getDirectlyInheritedNominalTypeDecls(
26842684
// FIXME: This is a hack. We need cooperation from
26852685
// InheritedDeclsReferencedRequest to make this work.
26862686
SourceLoc loc;
2687+
SourceLoc uncheckedLoc;
26872688
if (TypeRepr *typeRepr = typeDecl ? typeDecl->getInherited()[i].getTypeRepr()
26882689
: extDecl->getInherited()[i].getTypeRepr()){
26892690
loc = typeRepr->getLoc();
2691+
2692+
// Dig out the @unchecked attribute, if it's present.
2693+
while (auto attrTypeRepr = dyn_cast<AttributedTypeRepr>(typeRepr)) {
2694+
if (attrTypeRepr->getAttrs().has(TAK_unchecked)) {
2695+
uncheckedLoc = attrTypeRepr->getAttrs().getLoc(TAK_unchecked);
2696+
}
2697+
2698+
typeRepr = attrTypeRepr->getTypeRepr();
2699+
}
26902700
}
26912701

26922702
// Form the result.
26932703
for (auto nominal : nominalTypes) {
2694-
result.push_back({nominal, loc});
2704+
result.push_back({nominal, loc, uncheckedLoc});
26952705
}
26962706
}
26972707

2698-
SmallVector<Located<NominalTypeDecl *>, 4>
2708+
SmallVector<InheritedNominalEntry, 4>
26992709
swift::getDirectlyInheritedNominalTypeDecls(
27002710
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
27012711
bool &anyObject) {
@@ -2705,7 +2715,7 @@ swift::getDirectlyInheritedNominalTypeDecls(
27052715
// Gather results from all of the inherited types.
27062716
unsigned numInherited = typeDecl ? typeDecl->getInherited().size()
27072717
: extDecl->getInherited().size();
2708-
SmallVector<Located<NominalTypeDecl *>, 4> result;
2718+
SmallVector<InheritedNominalEntry, 4> result;
27092719
for (unsigned i : range(numInherited)) {
27102720
getDirectlyInheritedNominalTypeDecls(decl, i, result, anyObject);
27112721
}
@@ -2731,7 +2741,7 @@ swift::getDirectlyInheritedNominalTypeDecls(
27312741
if (!req.getFirstType()->isEqual(protoSelfTy))
27322742
continue;
27332743

2734-
result.emplace_back(req.getProtocolDecl(), loc);
2744+
result.emplace_back(req.getProtocolDecl(), loc, SourceLoc());
27352745
}
27362746
return result;
27372747
}
@@ -2741,7 +2751,7 @@ swift::getDirectlyInheritedNominalTypeDecls(
27412751
anyObject |= selfBounds.anyObject;
27422752

27432753
for (auto inheritedNominal : selfBounds.decls)
2744-
result.emplace_back(inheritedNominal, loc);
2754+
result.emplace_back(inheritedNominal, loc, SourceLoc());
27452755

27462756
return result;
27472757
}

lib/ClangImporter/ImportDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10043,7 +10043,8 @@ void ClangImporter::Implementation::loadAllConformances(
1004310043
auto conformance = SwiftContext.getConformance(
1004410044
dc->getDeclaredInterfaceType(),
1004510045
protocol, SourceLoc(), dc,
10046-
ProtocolConformanceState::Incomplete);
10046+
ProtocolConformanceState::Incomplete,
10047+
protocol->isSpecificProtocol(KnownProtocolKind::Sendable));
1004710048
conformance->setLazyLoader(this, /*context*/0);
1004810049
conformance->setState(ProtocolConformanceState::Complete);
1004910050
Conformances.push_back(conformance);

lib/Sema/CSDiagnostics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3113,7 +3113,7 @@ bool ContextualFailure::tryProtocolConformanceFixIt(
31133113
for (auto protocol : missingProtocols) {
31143114
auto conformance = NormalProtocolConformance(
31153115
nominal->getDeclaredType(), protocol, SourceLoc(), nominal,
3116-
ProtocolConformanceState::Incomplete);
3116+
ProtocolConformanceState::Incomplete, /*isUnchecked=*/false);
31173117
ConformanceChecker checker(getASTContext(), &conformance,
31183118
missingWitnesses);
31193119
checker.resolveValueWitnesses();

0 commit comments

Comments
 (0)